TypeScript Tutorial: Building a Simple Web Application for a File Compressor

In today’s digital world, files are constantly being shared, stored, and transferred. The larger the file, the longer it takes to upload, download, and store. This is where file compression becomes incredibly valuable. By reducing the size of files, we can save storage space, reduce bandwidth usage, and speed up data transfer. In this tutorial, we’ll dive into how to build a simple web application using TypeScript that allows users to compress files directly in their browser. This hands-on project will not only teach you practical skills but also provide a solid understanding of TypeScript fundamentals, file handling, and front-end development principles.

Why Build a File Compressor?

Building a file compressor web app is a fantastic way to learn and apply TypeScript. Here’s why:

  • Practical Application: File compression is a real-world problem with practical solutions. You’ll build something useful.
  • Hands-on Learning: You’ll get your hands dirty with code, experimenting with different techniques.
  • Frontend Focus: Learn about user interface design, event handling, and interacting with the browser’s APIs.
  • TypeScript Fundamentals: Reinforce your knowledge of types, classes, interfaces, and more.

Prerequisites

Before we begin, ensure you have the following:

  • Node.js and npm (or yarn) installed: These are essential for managing project dependencies and running the development server.
  • A text editor or IDE: Such as VS Code, Sublime Text, or WebStorm.
  • Basic understanding of HTML, CSS, and JavaScript: Familiarity with these technologies is helpful.

Project Setup

Let’s start by setting up our project. Open your terminal and create a new project directory:

mkdir file-compressor-app
cd file-compressor-app

Next, initialize a new Node.js project:

npm init -y

This command creates a package.json file, which will manage our project dependencies. Now, let’s install TypeScript and some development dependencies:

npm install typescript --save-dev
npm install @types/node --save-dev

We’ve installed TypeScript itself and the type definitions for Node.js, which will help with type checking. Next, let’s set up the TypeScript configuration file. Create a file named tsconfig.json in the root directory and add the following configuration:

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

This configuration tells TypeScript how to compile our code. Let’s break down some of the key options:

  • target: "es5": Specifies the JavaScript version to compile to.
  • module: "commonjs": Specifies the module system to use.
  • outDir: "./dist": Specifies the output directory for compiled JavaScript files.
  • rootDir: "./src": Specifies the root directory of your TypeScript source files.
  • strict: true: Enables strict type checking.
  • esModuleInterop: true: Enables interoperability between CommonJS and ES modules.
  • skipLibCheck: true: Skips type checking of declaration files.
  • forceConsistentCasingInFileNames: true: Enforces consistent casing in filenames.
  • include: ["src/**/*"]: Specifies the files and directories to include in the compilation.

Now, let’s create the project structure. We’ll have a src directory for our TypeScript source files, and a dist directory for the compiled JavaScript files. Create the src directory and inside it, create an index.ts file. This is where we’ll write our main application logic.

HTML Structure

Let’s create the basic HTML structure for our file compressor. Create an index.html file in the root directory of your project. This file will contain the user interface elements.




    
    
    <title>File Compressor</title>
    


    <div class="container">
        <h1>File Compressor</h1>
        
        <button id="compressButton">Compress</button>
        <div id="status"></div>
        <a id="downloadLink">Download Compressed File</a>
    </div>
    

Here’s a breakdown of the HTML elements:

  • <input type="file" id="fileInput">: Allows users to select a file from their computer.
  • <button id="compressButton">: Triggers the compression process.
  • <div id="status">: Displays the status of the compression process (e.g., “Compressing…”, “Done”).
  • <a id="downloadLink">: Provides a link to download the compressed file. Initially hidden.

Next, let’s add some basic styling to make it look presentable. Create a style.css file in the root directory and add the following CSS:

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

.container {
    background-color: #fff;
    padding: 20px;
    border-radius: 8px;
    box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
    text-align: center;
}

input[type="file"] {
    margin-bottom: 10px;
}

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

button:hover {
    background-color: #3e8e41;
}

#status {
    margin-top: 10px;
}

TypeScript Implementation

Now, let’s write the TypeScript code that will handle file selection, compression, and downloading. Open src/index.ts and add the following code:

// Get references to HTML elements
const fileInput = document.getElementById('fileInput') as HTMLInputElement;
const compressButton = document.getElementById('compressButton') as HTMLButtonElement;
const statusDiv = document.getElementById('status') as HTMLDivElement;
const downloadLink = document.getElementById('downloadLink') as HTMLAnchorElement;

// Function to compress the file (placeholder)
async function compressFile(file: File): Promise<Blob | null> {
    statusDiv.textContent = 'Compressing...';
    // Simulate compression (replace with actual compression logic)
    return new Promise<Blob | null>((resolve) => {
        setTimeout(() => {
            // Create a dummy compressed file (replace with actual compression)
            const compressedData = new Blob([file], { type: file.type });
            resolve(compressedData);
        }, 2000); // Simulate compression time
    });
}

// Event listener for the compress button
compressButton.addEventListener('click', async () => {
    const file = fileInput.files?.[0];
    if (!file) {
        statusDiv.textContent = 'Please select a file.';
        return;
    }

    try {
        const compressedFile = await compressFile(file);
        if (compressedFile) {
            // Create a download link
            const url = URL.createObjectURL(compressedFile);
            downloadLink.href = url;
            downloadLink.download = `${file.name}.compressed`; // Set the download filename
            downloadLink.style.display = 'block'; // Show the download link
            statusDiv.textContent = 'Compression complete. Download below.';
        } else {
            statusDiv.textContent = 'Compression failed.';
        }
    } catch (error) {
        console.error('Compression error:', error);
        statusDiv.textContent = 'An error occurred during compression.';
    }
});

Let’s break down the TypeScript code:

  • Element References: We get references to the HTML elements using their IDs.
  • compressFile Function: This is where the actual compression logic will reside. Currently, it’s a placeholder that simulates compression using setTimeout. We’ll replace this with a real compression algorithm later. It takes a File object as input and returns a Promise<Blob | null>.
  • Event Listener: An event listener is attached to the compress button. When clicked, it does the following:
  • Gets the selected file from the fileInput.
  • Calls the compressFile function to compress the file.
  • If the compression is successful, it creates a download link, sets the filename, and displays the link.
  • If an error occurs, it displays an error message.

Implementing Compression Logic (Using a Library)

The placeholder compressFile function currently doesn’t do any actual compression. Let’s integrate a compression library to achieve this. For this tutorial, we’ll use the pako library, a fast and lightweight JavaScript library for zlib compression and decompression. Install it using npm:

npm install pako

Now, let’s modify the compressFile function in src/index.ts to use pako:

import pako from 'pako';

// ... (other code)

async function compressFile(file: File): Promise<Blob | null> {
    statusDiv.textContent = 'Compressing...';

    return new Promise<Blob | null>((resolve, reject) => {
        const reader = new FileReader();

        reader.onload = () => {
            try {
                const uint8Array = new Uint8Array(reader.result as ArrayBuffer);
                const compressed = pako.deflate(uint8Array);
                const compressedBlob = new Blob([compressed], { type: 'application/octet-stream' });
                resolve(compressedBlob);
            } catch (error) {
                console.error('Compression error:', error);
                reject(error);
            }
        };

        reader.onerror = (error) => {
            console.error('File read error:', error);
            reject(error);
        };

        reader.readAsArrayBuffer(file);
    });
}

Here’s what’s changed:

  • Import pako: We import the pako library.
  • FileReader: We use the FileReader API to read the file’s content as an ArrayBuffer.
  • ArrayBuffer to Uint8Array: We convert the ArrayBuffer to a Uint8Array, which pako uses.
  • Compression with pako.deflate: We use pako.deflate to compress the data.
  • Create Compressed Blob: We create a new Blob from the compressed data.
  • Error Handling: We added error handling within the try...catch block and for file reading.

Building and Running the Application

Now that we have the HTML, CSS, and TypeScript code, let’s build and run the application. Open your terminal and run the following command to compile the TypeScript code:

tsc

This command will compile the TypeScript code in src/index.ts and generate a dist/index.js file. Now, open index.html in your web browser. You should see the file compressor interface. Select a file, click “Compress”, and after a short delay, a download link should appear. Click the link to download the compressed file.

Common Mistakes and How to Fix Them

Here are some common mistakes and how to avoid them:

  • Incorrect File Paths: Double-check that your file paths in index.html (e.g., for the CSS and JavaScript files) are correct.
  • Type Errors: TypeScript’s type checking can help prevent errors. Make sure you’re using types correctly and that your code compiles without any errors. Review the error messages carefully.
  • Asynchronous Operations: Remember that file reading and compression are asynchronous operations. Use async/await or Promises to handle them correctly.
  • Missing Dependencies: Make sure you’ve installed all the necessary dependencies (e.g., pako) using npm.
  • Incorrect File Types: Ensure that the type attribute of the Blob when creating the compressed file is correct. For generic compression, using application/octet-stream is a safe option.
  • Browser Compatibility: While modern browsers support the features we’re using, older browsers might have compatibility issues. Consider using a polyfill for features like FileReader if you need to support older browsers.

Enhancements and Next Steps

Here are some ways to enhance the file compressor application:

  • Compression Type Selection: Allow users to choose different compression algorithms (e.g., gzip, bzip2) or compression levels.
  • Progress Bar: Implement a progress bar to show the compression progress.
  • File Type Handling: Add specific handling for different file types (e.g., images, text files) to optimize compression.
  • Drag-and-Drop: Implement drag-and-drop functionality for file uploads.
  • Error Handling Improvements: Provide more user-friendly error messages and handle different types of errors gracefully.
  • UI/UX improvements: Improve the user interface, add animations, and provide better visual feedback.

Summary / Key Takeaways

In this tutorial, we’ve built a simple yet functional file compressor web application using TypeScript. We’ve covered project setup, HTML structure, CSS styling, and the core TypeScript logic for file selection, compression (using the pako library), and downloading. You’ve learned about essential concepts like file handling, asynchronous operations, and DOM manipulation. By following this guide, you should now have a solid understanding of how to use TypeScript to create interactive web applications that interact with files. Remember to experiment with the code, try different compression algorithms, and explore the enhancements mentioned to further your skills.

FAQ

  1. Why use TypeScript for this project? TypeScript provides static typing, which helps catch errors early, improves code readability, and makes the project more maintainable as it grows.
  2. What is the purpose of the pako library? pako is a fast and lightweight JavaScript library for zlib compression and decompression. It allows us to compress the file data in the browser.
  3. How does the compression work? The pako.deflate function uses the DEFLATE algorithm to compress the file data. The algorithm reduces the file size by finding and replacing repeated patterns with shorter representations.
  4. Can I use this compressor for any file type? Yes, you can compress any file type. However, the compression ratio may vary depending on the file type. Files with a lot of redundant data (e.g., text files) will generally compress better than files that are already compressed (e.g., ZIP files).
  5. What are the limitations of this in-browser compression? The main limitation is that the compression happens in the user’s browser, which can be slower than server-side compression, especially for large files. Also, the user’s browser resources are used for the compression.

As you continue your journey in web development, remember that this file compressor is more than just a project; it’s a foundation. It demonstrates how to bring real-world functionality to the web, empowering users with tools that enhance their digital experiences. By understanding the principles we’ve covered, from the initial setup to the integration of compression libraries, you’re well-equipped to tackle more complex projects. Keep learning, keep experimenting, and embrace the ever-evolving world of web development. The skills you’ve gained here are stepping stones, paving the way for further exploration and creation. With each line of code, you’re not just building an application; you’re building your expertise. So, keep building, keep learning, and keep creating – the possibilities are endless.