In the world of web development, writing clean, readable, and maintainable code is crucial. It’s not just about getting the code to work; it’s about making it easy for you and others to understand, modify, and debug. One of the best ways to achieve this is by using a code formatter. A code formatter automatically adjusts the spacing, indentation, and overall structure of your code, ensuring consistency and readability across your projects. This tutorial will guide you through building a simple, web-based code formatter using TypeScript. We’ll explore the core concepts, step-by-step implementation, and address common pitfalls.
Why Code Formatting Matters
Imagine reading a book where the paragraphs are all jumbled together, with no spaces between sentences and inconsistent indentation. It would be a nightmare, right? Code is no different. Poorly formatted code is difficult to read and understand, leading to:
- Increased debugging time: It takes longer to spot errors when the code is messy.
- Higher maintenance costs: Modifying poorly formatted code is more challenging and error-prone.
- Reduced collaboration efficiency: Team members struggle to understand each other’s code.
A good code formatter solves these problems by automatically:
- Enforcing consistent style guidelines.
- Improving code readability.
- Reducing the chances of human error.
Core Concepts: TypeScript and Prettier
Before we dive into the implementation, let’s briefly discuss the technologies we’ll be using:
TypeScript
TypeScript is a superset of JavaScript that adds static typing. This means you can define the data types of variables, function parameters, and return values. TypeScript helps catch errors early in the development process, making your code more reliable and easier to maintain. It also provides better code completion and refactoring capabilities in your IDE.
Prettier
Prettier is an opinionated code formatter. It takes your code and automatically formats it to adhere to a consistent style. Prettier supports various languages, including JavaScript, TypeScript, HTML, CSS, and more. It eliminates the need for manual formatting and ensures that your code always looks clean and consistent.
Setting Up Your Project
Let’s get started by setting up our project. We’ll use a simple HTML file, a TypeScript file, and install Prettier as a development dependency. Follow these steps:
- Create a new project directory (e.g., `code-formatter`).
- Navigate into the directory using your terminal: `cd code-formatter`.
- Initialize a new Node.js project: `npm init -y`. This creates a `package.json` file.
- Install TypeScript and Prettier as development dependencies: `npm install –save-dev typescript prettier`.
- Create a `tsconfig.json` file. You can generate a basic one using the TypeScript compiler: `npx tsc –init`. This file configures the TypeScript compiler.
- Create an HTML file named `index.html` in your project directory.
- Create a TypeScript file named `app.ts` in your project directory.
Your project structure should look something like this:
code-formatter/
├── index.html
├── app.ts
├── package.json
├── tsconfig.json
└── node_modules/ (This directory is created when you install npm packages)
Building the HTML Structure
Let’s create a basic HTML structure for our code formatter. 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 Formatter</title>
<style>
body {
font-family: sans-serif;
margin: 20px;
}
textarea {
width: 100%;
height: 200px;
margin-bottom: 10px;
}
</style>
</head>
<body>
<textarea id="codeInput" placeholder="Enter your code here"></textarea>
<button id="formatButton">Format Code</button>
<textarea id="codeOutput" placeholder="Formatted code"></textarea>
<script src="app.js"></script>
</body>
</html>
This HTML creates a simple interface with:
- A `textarea` for the user to input their code (`codeInput`).
- A button to trigger the formatting (`formatButton`).
- Another `textarea` to display the formatted code (`codeOutput`).
- A link to the `app.js` file, which we’ll generate from our `app.ts` file.
Writing the TypeScript Logic
Now, let’s write the TypeScript code that will handle the formatting. Open `app.ts` and add the following code:
import prettier from "prettier";
const codeInput = document.getElementById("codeInput") as HTMLTextAreaElement;
const formatButton = document.getElementById("formatButton") as HTMLButtonElement;
const codeOutput = document.getElementById("codeOutput") as HTMLTextAreaElement;
async function formatCode() {
if (!codeInput || !codeOutput) {
console.error("Code input or output element not found.");
return;
}
const code = codeInput.value;
if (!code) {
codeOutput.value = "";
return;
}
try {
const formattedCode = await prettier.format(code, {
parser: "babel", // Or your preferred parser (e.g., "typescript", "html", "css")
plugins: [], // You can add Prettier plugins here
});
codeOutput.value = formattedCode;
} catch (error: any) {
console.error("Formatting error:", error);
codeOutput.value = `Formatting error: ${error.message}`;
}
}
formatButton?.addEventListener("click", formatCode);
Let’s break down this code:
- We import the `prettier` library.
- We get references to the HTML elements (input, button, and output textareas).
- The `formatCode` function is an `async` function that does the following:
- Gets the code from the input textarea.
- Uses `prettier.format()` to format the code. The `parser` option specifies the language of the code (in this case, “babel” which is suitable for JavaScript, but change to “typescript”, “html”, etc., as needed). The `plugins` array is where you can include Prettier plugins for additional formatting capabilities.
- Displays the formatted code in the output textarea.
- Handles any formatting errors.
- We add an event listener to the format button to call the `formatCode` function when clicked.
Compiling and Running the Code
Now, let’s compile the TypeScript code into JavaScript and run it in the browser:
- Open your terminal and navigate to your project directory.
- Compile the TypeScript code: `npx tsc`. This will generate an `app.js` file.
- Open `index.html` in your web browser.
- Enter some code into the input textarea.
- Click the “Format Code” button.
- The formatted code should appear in the output textarea.
Addressing Common Mistakes
Here are some common mistakes and how to fix them:
1. Prettier Not Installed
If you get an error that Prettier is not found, make sure you have installed it as a development dependency using `npm install –save-dev prettier`.
2. Incorrect Parser
If the code is not formatted correctly, or if you get an error, the parser might be incorrect. Ensure that the `parser` option in `prettier.format()` is set to the correct language (e.g., “babel” for JavaScript, “typescript” for TypeScript, “html” for HTML, “css” for CSS). For example, if you are formatting HTML, your `prettier.format()` call might look like this:
const formattedCode = await prettier.format(code, {
parser: "html",
plugins: [],
});
3. Missing HTML Element References
Make sure that the `id` attributes in your HTML match the element references in your TypeScript code. For example, if your HTML has `
4. Incorrect TypeScript Compilation
If you are encountering errors during the compilation of your TypeScript code, double-check your `tsconfig.json` file to ensure it is correctly configured. Make sure the `target` option is set to an appropriate JavaScript version (e.g., “ES2015”, “ESNext”) and that the `module` option is set to a module system appropriate for your project (e.g., “commonjs”, “esnext”).
Adding Features: Customization and Error Handling
Let’s enhance our code formatter by adding more features:
1. Customization Options
We can allow users to customize the formatting by providing options. Prettier supports many options, such as `tabWidth`, `useTabs`, and `semi`. We can add a form to the HTML to allow users to select their preferred options. Let’s add a few basic options like tab width and use tabs.
First, update your `index.html` to include the options:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Code Formatter</title>
<style>
body {
font-family: sans-serif;
margin: 20px;
}
textarea {
width: 100%;
height: 200px;
margin-bottom: 10px;
}
label {
display: block;
margin-bottom: 5px;
}
</style>
</head>
<body>
<textarea id="codeInput" placeholder="Enter your code here"></textarea>
<div>
<label for="tabWidth">Tab Width:</label>
<input type="number" id="tabWidth" value="2">
</div>
<div>
<label for="useTabs">Use Tabs:</label>
<input type="checkbox" id="useTabs">
</div>
<button id="formatButton">Format Code</button>
<textarea id="codeOutput" placeholder="Formatted code"></textarea>
<script src="app.js"></script>
</body>
</html>
Next, modify your `app.ts` file to read these options and pass them to Prettier:
import prettier from "prettier";
const codeInput = document.getElementById("codeInput") as HTMLTextAreaElement;
const formatButton = document.getElementById("formatButton") as HTMLButtonElement;
const codeOutput = document.getElementById("codeOutput") as HTMLTextAreaElement;
const tabWidthInput = document.getElementById("tabWidth") as HTMLInputElement;
const useTabsInput = document.getElementById("useTabs") as HTMLInputElement;
async function formatCode() {
if (!codeInput || !codeOutput) {
console.error("Code input or output element not found.");
return;
}
const code = codeInput.value;
if (!code) {
codeOutput.value = "";
return;
}
const tabWidth = parseInt(tabWidthInput?.value || "2", 10);
const useTabs = useTabsInput?.checked || false;
try {
const formattedCode = await prettier.format(code, {
parser: "babel", // Or your preferred parser
tabWidth: tabWidth,
useTabs: useTabs,
plugins: [],
});
codeOutput.value = formattedCode;
} catch (error: any) {
console.error("Formatting error:", error);
codeOutput.value = `Formatting error: ${error.message}`;
}
}
formatButton?.addEventListener("click", formatCode);
In this updated code:
- We get references to the new HTML input elements (`tabWidthInput`, `useTabsInput`).
- We read the values from these inputs and pass them as options to `prettier.format()`.
2. Error Handling Improvements
We can improve error handling by providing more specific error messages to the user. Prettier can throw different types of errors, such as syntax errors or configuration errors. We can display these errors in the output textarea to help the user understand what went wrong.
Here’s how you can enhance the error handling in your `app.ts`:
import prettier from "prettier";
const codeInput = document.getElementById("codeInput") as HTMLTextAreaElement;
const formatButton = document.getElementById("formatButton") as HTMLButtonElement;
const codeOutput = document.getElementById("codeOutput") as HTMLTextAreaElement;
const tabWidthInput = document.getElementById("tabWidth") as HTMLInputElement;
const useTabsInput = document.getElementById("useTabs") as HTMLInputElement;
async function formatCode() {
if (!codeInput || !codeOutput) {
console.error("Code input or output element not found.");
return;
}
const code = codeInput.value;
if (!code) {
codeOutput.value = "";
return;
}
const tabWidth = parseInt(tabWidthInput?.value || "2", 10);
const useTabs = useTabsInput?.checked || false;
try {
const formattedCode = await prettier.format(code, {
parser: "babel", // Or your preferred parser
tabWidth: tabWidth,
useTabs: useTabs,
plugins: [],
});
codeOutput.value = formattedCode;
} catch (error: any) {
let errorMessage = "Formatting error:";
if (error.message) {
errorMessage += ` ${error.message}`;
}
if (error.loc) {
errorMessage += ` at line ${error.loc.line}, column ${error.loc.column}`;
}
codeOutput.value = errorMessage;
console.error("Formatting error:", error);
}
}
formatButton?.addEventListener("click", formatCode);
In the updated error handling:
- We check for `error.message` to get the general error message.
- We check for `error.loc` (location) to display the line and column number where the error occurred, if available. This is especially helpful for syntax errors.
- We provide a more informative error message in the output textarea.
Adding a Language Selector
Our code formatter currently assumes the code is JavaScript (or whatever you set the parser to). Let’s add a language selector so the user can specify the language of the code they’re formatting. This will allow the user to format different types of code, such as HTML, CSS, or TypeScript.
First, modify your `index.html` to include a select element for the language:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Code Formatter</title>
<style>
body {
font-family: sans-serif;
margin: 20px;
}
textarea {
width: 100%;
height: 200px;
margin-bottom: 10px;
}
label {
display: block;
margin-bottom: 5px;
}
</style>
</head>
<body>
<textarea id="codeInput" placeholder="Enter your code here"></textarea>
<div>
<label for="language">Language:</label>
<select id="language">
<option value="babel">JavaScript</option>
<option value="typescript">TypeScript</option>
<option value="html">HTML</option>
<option value="css">CSS</option>
</select>
</div>
<div>
<label for="tabWidth">Tab Width:</label>
<input type="number" id="tabWidth" value="2">
</div>
<div>
<label for="useTabs">Use Tabs:</label>
<input type="checkbox" id="useTabs">
</div>
<button id="formatButton">Format Code</button>
<textarea id="codeOutput" placeholder="Formatted code"></textarea>
<script src="app.js"></script>
</body>
</html>
Next, modify your `app.ts` file to read the selected language and pass it to Prettier:
import prettier from "prettier";
const codeInput = document.getElementById("codeInput") as HTMLTextAreaElement;
const formatButton = document.getElementById("formatButton") as HTMLButtonElement;
const codeOutput = document.getElementById("codeOutput") as HTMLTextAreaElement;
const tabWidthInput = document.getElementById("tabWidth") as HTMLInputElement;
const useTabsInput = document.getElementById("useTabs") as HTMLInputElement;
const languageSelect = document.getElementById("language") as HTMLSelectElement;
async function formatCode() {
if (!codeInput || !codeOutput) {
console.error("Code input or output element not found.");
return;
}
const code = codeInput.value;
if (!code) {
codeOutput.value = "";
return;
}
const tabWidth = parseInt(tabWidthInput?.value || "2", 10);
const useTabs = useTabsInput?.checked || false;
const selectedLanguage = languageSelect?.value || "babel"; // Default to JavaScript
try {
const formattedCode = await prettier.format(code, {
parser: selectedLanguage, // Use the selected language
tabWidth: tabWidth,
useTabs: useTabs,
plugins: [],
});
codeOutput.value = formattedCode;
} catch (error: any) {
let errorMessage = "Formatting error:";
if (error.message) {
errorMessage += ` ${error.message}`;
}
if (error.loc) {
errorMessage += ` at line ${error.loc.line}, column ${error.loc.column}`;
}
codeOutput.value = errorMessage;
console.error("Formatting error:", error);
}
}
formatButton?.addEventListener("click", formatCode);
In this updated code:
- We get a reference to the language select element (`languageSelect`).
- We get the selected language from the `value` property of the select element.
- We pass the selected language as the `parser` option to `prettier.format()`.
Testing and Further Enhancements
After implementing these features, thoroughly test your code formatter. Try different code snippets in various languages (JavaScript, TypeScript, HTML, CSS) and verify that the formatting is correct. Check how the custom options affect the output. Also, test the error handling by introducing syntax errors and ensuring that the error messages are displayed correctly.
Here are some further enhancements you can consider:
- **Syntax Highlighting:** Integrate a syntax highlighting library (e.g., Prism.js, highlight.js) to display the formatted code with syntax highlighting, making it even easier to read.
- **Code Mirror Integration:** Use a code editor like CodeMirror to provide a more advanced code editing experience, including features like code completion and error checking.
- **Save/Load Functionality:** Add the ability to save the formatted code to a file or load code from a file.
- **Theme Customization:** Allow users to choose different themes for the code editor and output area.
- **Real-time Formatting:** Implement real-time formatting, where the code is formatted automatically as the user types.
- **Plugin Support:** Allow users to install and use Prettier plugins to extend the formatting capabilities.
Key Takeaways
- Code formatters are essential for writing clean, readable, and maintainable code.
- TypeScript adds static typing to JavaScript, improving code reliability.
- Prettier is a powerful and opinionated code formatter that automates the formatting process.
- Building a web-based code formatter with TypeScript and Prettier is a straightforward process.
- You can customize and enhance your code formatter with features like customization options, error handling, and language selection.
FAQ
Here are some frequently asked questions about building a code formatter:
- Can I use a different code formatter instead of Prettier? Yes, you can. While Prettier is a popular choice, you can use other formatters, such as ESLint with a code formatting plugin. However, Prettier’s ease of use and consistent results make it a great starting point.
- How do I handle different file types? The `parser` option in `prettier.format()` is crucial for handling different file types. Make sure to set the correct parser for each file type (e.g., “babel” for JavaScript, “typescript” for TypeScript, “html” for HTML, “css” for CSS).
- How can I integrate syntax highlighting? You can integrate syntax highlighting libraries like Prism.js or highlight.js. Load the library and apply it to the formatted code in the output textarea.
- How can I add more advanced editing features? You can integrate a code editor library like CodeMirror or Monaco Editor to provide features like code completion, error checking, and more.
- How do I handle large code files? For large code files, consider using a code editor library designed for performance and handling large text documents efficiently. Also, consider optimizing your Prettier configuration for performance.
Building a web-based code formatter is a great way to learn about TypeScript, Prettier, and web development in general. It also provides a practical tool that can improve your coding workflow. By following the steps in this tutorial and experimenting with the enhancements, you can create a powerful and customizable code formatter to suit your specific needs. Remember that the key to mastering any technology is practice. The more you experiment, the more comfortable you will become with the tools and techniques. Keep exploring, keep building, and enjoy the process of creating useful and efficient software.
