I have been looking for a simple way to use typescript in my html projects for some time now. There is Webpack, but it needs a lot of configuration for even the simplest of things. There is always the option to use React or Angular, but that is too much for something quick. Then there is Parcel.js

Parcel.js is a fast, zero-config web application bundler that makes setting up a development environment a breeze. It comes with built-in support for TypeScript, which can be integrated into HTML with minimal hassle. Whether you’re building a simple project or something more complex, Parcel makes it easy to get up and running. In this article, we’ll explore how to use Parcel.js to set up a project with TypeScript, structure your project for scalability, and prepare it for deployment.


What You’ll Learn

  • How to set up a Parcel.js project
  • How to integrate TypeScript into your HTML
  • The ideal project layout structure for Parcel.js projects
  • How to build and deploy your project with Parcel.js

Setting Up Your Project

To get started with Parcel.js and TypeScript, you’ll first need to create a new project folder. Then, initialize your project with npm or yarn.

  1. Initialize the Project

    First, navigate to your project folder in your terminal and run:

    mkdir parcel-typescript-demo
    cd parcel-typescript-demo
    npm init -y
    

    This will create a package.json file with default values.

  2. Install Parcel and TypeScript

    Next, install Parcel and TypeScript as development dependencies:

    npm install --save-dev parcel typescript
    
  3. Add TypeScript Configuration

    Create a tsconfig.json file to configure TypeScript. This will tell TypeScript how to handle your code.

    {
      "compilerOptions": {
        "target": "es6",
        "module": "esnext",
        "strict": true,
        "jsx": "react"
      },
      "include": ["src/**/*"]
    }
    
  4. Create Your HTML and TypeScript Files

    Now you’re ready to create your files. In the root directory, create the following structure:

    /parcel-typescript-demo
    ├── src
    │   ├── index.html
    │   └── app.ts
    ├── package.json
    ├── tsconfig.json
    

Adding TypeScript to HTML

In index.html, you can add a <script> tag to link to your TypeScript file. Parcel handles the compilation for you, so you don’t need to manually transpile TypeScript before using it in HTML.

Example: index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Parcel.js TypeScript Demo</title>
  </head>
  <body>
    <h1>Welcome to Parcel.js with TypeScript!</h1>

    <!-- Link to the TypeScript file -->
    <script src="./app.ts"></script>
  </body>
</html>

Example: app.ts

In app.ts, you can start writing TypeScript code. Parcel will automatically bundle it when you run the development server.

const message: string = "Hello, Parcel with TypeScript!";
console.log(message);

const heading = document.querySelector("h1");
if (heading) {
  heading.textContent = message;
}

Here, you declare a variable message of type string, and then modify the content of the <h1> tag in your HTML based on that message.


Dev Server

You can serve the project by running parcel on index.html

parcel index.html

And that’s it!

Structuring Your Project

As your project grows, it’s important to have a well-organized structure. Here’s a basic layout that you can use for most projects:

/parcel-typescript-demo
├── src/
│   ├── assets/           # Static files like images and styles
│   ├── components/       # Reusable UI components (if any)
│   ├── index.html        # Main HTML file
│   └── app.ts            # Main TypeScript file
├── dist/                 # Compiled output (generated by Parcel)
├── node_modules/         # Node.js modules
├── package.json          # Project configuration
├── tsconfig.json         # TypeScript configuration

Key Directories:

  • src: This is where your main code lives. Keep all your TypeScript files, HTML files, and static assets (like CSS or images) here.
  • dist: The dist/ folder will contain the bundled files after running the build command. This is where your production-ready files will be placed.
  • node_modules: This folder contains all the packages installed via npm or yarn.

Building for Deployment

Once your project is ready, it’s time to build it for production. Parcel makes this process simple with just one command:

  1. Build Your Project

    To build your project for production, run:

    npx parcel build src/index.html
    

    This will create a dist folder with all your bundled and minified files, ready to be deployed.

  2. Deploy Your Project

    The contents of the dist folder are all that you need to deploy your application. You can upload them to your favorite hosting provider, such as Netlify, Vercel, or GitHub Pages.

    For example, if you’re using GitHub Pages, you can push the dist/ folder to your GitHub repository and configure the pages to serve from the dist folder.


Examples

If you are just starting out with Typescript the following examples might come in handy.


Example 1: TypeScript Classes and DOM Manipulation

In this example, we’ll create a simple Button class in TypeScript that interacts with the DOM. This will show how you can write more structured TypeScript code and integrate it with HTML.

Example: Button.ts

Create a new file called Button.ts under src/:

export class Button {
  private element: HTMLButtonElement;

  constructor(label: string) {
    this.element = document.createElement("button");
    this.element.textContent = label;
    document.body.appendChild(this.element);
  }

  addClickListener(callback: () => void): void {
    this.element.addEventListener("click", callback);
  }
}

Example: app.ts

Now, modify app.ts to use the Button class:

import { Button } from "./Button";

const greetButton = new Button("Click me to greet");

greetButton.addClickListener(() => {
  alert("Hello from Parcel + TypeScript!");
});

Explanation:

  • Button.ts defines a simple Button class with methods for adding a label and listening for click events.
  • app.ts creates an instance of Button and listens for clicks, displaying an alert when clicked.

This example shows how to encapsulate code using TypeScript classes and interact with the DOM in a structured way.


Example 2: Using TypeScript Interfaces for Type Checking

TypeScript’s interface feature allows you to define the shape of an object. This is especially useful when you need to ensure that objects have the correct properties.

Example: User.ts

Create a file called User.ts:

export interface User {
  name: string;
  age: number;
}

export function greetUser(user: User): string {
  return `Hello, ${user.name}! You are ${user.age} years old.`;
}

Example: app.ts

Modify app.ts to use the User interface:

import { User, greetUser } from "./User";

const user: User = {
  name: "Aliya",
  age: 25,
};

console.log(greetUser(user));

Explanation:

  • User.ts defines an interface User that specifies the structure of a user object. The greetUser function uses this interface for type-checking.
  • app.ts creates an object that adheres to the User interface and uses the greetUser function to print a greeting.

This shows how to use TypeScript’s type-checking system with interfaces to define structured data, helping to catch potential errors early.


Example 3: Handling Events with TypeScript and the DOM

TypeScript allows you to type-check events, making it easier to work with event listeners and handlers. In this example, we’ll create an event listener for a form submission.

Example: Form.ts

Create a new file called Form.ts:

export class Form {
  private form: HTMLFormElement;
  private input: HTMLInputElement;

  constructor(formId: string) {
    this.form = document.getElementById(formId) as HTMLFormElement;
    this.input = this.form.querySelector("input") as HTMLInputElement;
  }

  addSubmitListener(callback: (inputValue: string) => void): void {
    this.form.addEventListener("submit", (event: Event) => {
      event.preventDefault();
      callback(this.input.value);
    });
  }
}

Example: app.ts

Now, modify app.ts to use the Form class:

import { Form } from "./Form";

const form = new Form("myForm");

form.addSubmitListener((inputValue) => {
  alert(`You submitted: ${inputValue}`);
});

Example: index.html

Make sure to include the form in your index.html:

<form id="myForm">
  <input type="text" placeholder="Enter something" />
  <button type="submit">Submit</button>
</form>

Explanation:

  • Form.ts defines a Form class that listens for a form submission and uses type checking to ensure the correct handling of form elements.
  • app.ts creates a form listener that triggers an alert when the form is submitted, showing the entered input.

This example demonstrates how to work with form events in TypeScript, adding type safety and structure.


Example 4: Using TypeScript with External APIs (Fetch)

In this example, we will fetch data from an external API using TypeScript and handle the response with types.

Example: Api.ts

Create a file called Api.ts:

interface Post {
  userId: number;
  id: number;
  title: string;
  body: string;
}

export async function fetchPosts(): Promise<Post[]> {
  const response = await fetch("https://jsonplaceholder.typicode.com/posts");
  const posts: Post[] = await response.json();
  return posts;
}

Example: app.ts

Modify app.ts to fetch and display the posts:

import { fetchPosts } from "./Api";

async function displayPosts() {
  const posts = await fetchPosts();
  const postsList = document.createElement("ul");

  posts.forEach((post) => {
    const listItem = document.createElement("li");
    listItem.textContent = `${post.title} - ${post.body}`;
    postsList.appendChild(listItem);
  });

  document.body.appendChild(postsList);
}

displayPosts();

Explanation:

  • Api.ts defines a function fetchPosts that fetches posts from a public API and returns the response typed as an array of Post objects.
  • app.ts calls fetchPosts to retrieve data and displays it on the web page.

This example shows how TypeScript helps ensure that your API responses are correctly typed, preventing common errors when working with external data.


Example 5: Using TypeScript with Modules

Parcel supports ES modules out of the box. In this example, we’ll split our code into multiple modules, demonstrating how to import and export functions or classes.

Example: mathUtils.ts

Create a file called mathUtils.ts:

export function add(a: number, b: number): number {
  return a + b;
}

export function subtract(a: number, b: number): number {
  return a - b;
}

Example: app.ts

Now, modify app.ts to use the functions from mathUtils.ts:

import { add, subtract } from "./mathUtils";

const sum = add(5, 3);
const difference = subtract(10, 4);

console.log(`Sum: ${sum}, Difference: ${difference}`);

Explanation:

  • mathUtils.ts defines two functions: add and subtract, which are exported to be used in other modules.
  • app.ts imports those functions and uses them to perform simple arithmetic operations.

This shows how to structure your TypeScript code into reusable modules.


Parcel.js simplifies web development by providing automatic handling of TypeScript integration, which allows you to focus more on writing code rather than managing configurations. In this tutorial, we covered:

  • How to set up a Parcel.js project with TypeScript.
  • How to integrate TypeScript into your HTML files.
  • Structuring your project for maintainability and scalability.
  • Building and deploying your project with a few simple commands.

PS: If you’re just starting out with TypeScript, consider enabling strict mode in your tsconfig.json. It forces you to handle edge cases and can prevent bugs before they become problems!