Skip to content

How to Build a LitElement Application with Rollup.js and TypeScript

This article was written over 18 months ago and may contain information that is out of date. Some content may be relevant but please refer to the relevant official documentation or available resources for the latest information.

In previous posts, I explained How to setup a TypeScript project using Rollup.js from scratch. Also, I covered How to Serve a Single Page Application(SPA) using Rollup.js and Web Dev Server step by step.

In this article, I'll take the result of both tutorials as a starting point to integrate LitElement into the game to write the first Web Components through the latest web standards and powerful TypeScript features.

Project Setup

Prerequisites

You'll need to have installed the following tools in your local environment:

  • Node.js. Preferably the latest LTS version.
  • A package manager. You can use either NPM or Yarn. This tutorial will use NPM.

Initialize the Project

Let's create a clone or download the project seed before adding the new configurations and tools:

git clone https://github.com/luixaviles/typescript-rollup.git
cd typescript-rollup/
git checkout tags/02-serve-spa -b 03-litelement-app

The previous commands will download the project and create a new branch 03-litelement-app to get started.

Source Code Files

The previous project already contains a set of files and configurations ready to go with TypeScript and Rollup. Open it with your favorite code editor and take a look at the project structure:

|- typescript-rollup
    |- src/
        |- math/
            |- math.ts
            |- index.ts
        |- string/
            |- string.ts
            |- index.ts
        |- app.ts
    |- index.html
    |- package.json
    |- rollup.config.js
    |- tsconfig.json
    |- web-dev-server.config.js

Installing LitElement and lit-html

The Single-Page Application will be based on LitElement, hence we'll need to install some development dependencies to the project:

npm install --save lit-element lit-html
  • LitElement will be the main tool for writing the class-based web components for the application.
  • lit-html is an efficient and extensible HTML templating library.

Your package.json file should have the new dependencies listed as follows:

{
  ...

  "dependencies": {
    "lit-element": "^2.4.0",
    "lit-html": "^1.3.0"
  }
}

Rollup Configuration

The project is already configured and it uses Rollup as the module bundler. Verify the content of rollup.config.js as follows:

// rollup.config.js
import merge from 'deepmerge';
import { createSpaConfig } from '@open-wc/building-rollup';

const baseConfig = createSpaConfig({
  developmentMode: process.env.ROLLUP_WATCH === 'true',
  injectServiceWorker: false
});

export default merge(baseConfig, {
  // any <script type="module"> inside will be bundled by rollup
  input: './index.html'
});

The baseConfig content will be generated from createSpaConfig function, which is defined in @open-wc/building-rollup package.

When createSpaConfig is used, a service worker is generated using Workbox. However, the service worker is injected into the index.html file when the injectServiceWorker flag is enabled.

The Serve and Build Scripts

Now it's time to pay attention to the package.json file. It already defines a set of scripts to both serve and build the project:

{
  ...
  "scripts": {
    "start": "concurrently --kill-others --names tsc,web-dev-server \"npm run tsc:watch\" \"web-dev-server --config web-dev-server.config.js\"",
    "build": "rimraf dist && tsc && rollup -c rollup.config.js"
  },
}

Let's explain what's going to happen with those scripts:

  • "tsc:watch" is the script to start with the compilation process through tsc, which is the TypeScript compiler. The --watch param stands for a compiler option to run the compiler in watch mode(it trigger recompilation on file changes).
  • "start" is the script to add the execution of some commands in parallel: npm run tsc:watch and web-dev-server that includes some CLI flags through a configuration file.
    • You already noted that concurrently command is called first!
    • The --kill-others parameter will kill all the invoked commands if one dies(either tsc or web-dev-server).

It's important to note that any time you need to configure or customize the web-dev-server behavior, the web-dev-server.config.js file should be updated.

TypeScript Configuration

The TypeScript configuration is defined in tsconfig.json file. It already contains some relevant content for the compilation process:

{
  "compilerOptions": {
    "target": "es2018",                          
    "module": "esnext",
    "moduleResolution": "node",                     
    "noEmitOnError": true,
    "lib": ["es2017"],                            
    "strict": true,  
    "esModuleInterop": false,                 
    "outDir": "out-tsc",
    "rootDir": "./"
  }
  ,
  "include": ["./src/**/*.ts"]
}

However, it would be good to add some capabilities to start working with LitElement. To do that, let's add the experimentalDecorators support along with the dom library(to be included in the compilation process). The final state of this fill would be:

{
  "compilerOptions": {
    "target": "es2018",                          
    "module": "esnext",
    "moduleResolution": "node",                     
    "noEmitOnError": true,
    "lib": ["es2017", "dom"],                            
    "strict": true,  
    "esModuleInterop": false,                 
    "outDir": "out-tsc",
    "rootDir": "./",
    "experimentalDecorators": true,
  }
  ,
  "include": ["./src/**/*.ts"]
}
  • "experimentalDecorators": true provides the Decorators support for TypeScript. It involves the use of annotations and meta-programming syntax for class declarations and attributes. LitElement support them too. For example:
import {LitElement, html, customElement, property} from 'lit-element';

@customElement('my-custom-element')
class MyCustomElement extends LitElement {

 @property()
 propName = 'value';

 render() {
   return html`Hello world`;
 }
}

Both @customElement() and @property() decorators will make the web components definition compact.

  • "lib": ["es2017", "dom"] as the name states, provides a configuration with a list of library files to be included in the compilation. Take a look at the list of possible values for this parameter in the official documentation.

Just to clarify, without adding dom into lib configuration, you may find several errors at compilation time. For example:

[tsc] node_modules/lit-element/lib/css-tag.d.ts(16,19): error TS2304: Cannot find name 'CSSStyleSheet'.
[tsc] node_modules/lit-element/lib/decorators.d.ts(46,108): error TS2304: Cannot find name 'HTMLElement'.
[tsc] node_modules/lit-element/lib/decorators.d.ts(203,47): error TS2304: Cannot find name 'AddEventListenerOptions'.
[tsc] node_modules/lit-element/lit-element.d.ts(105,49): error TS2304: Cannot find name 'Element'.
[tsc] node_modules/lit-element/lit-element.d.ts(105,59): error TS2304: Cannot find name 'DocumentFragment'.
[tsc] node_modules/lit-element/lit-element.d.ts(140,45): error TS2304: Cannot find name 'ShadowRoot'.
[tsc] node_modules/lit-html/lib/dom.d.ts(23,49): error TS2304: Cannot find name 'Node'.
[tsc] node_modules/lit-html/lib/parts.d.ts(137,29): error TS2304: Cannot find name 'EventTarget'.
[tsc] node_modules/lit-html/lib/render.d.ts(32,69): error TS2304: Cannot find name 'DocumentFragment'.
[tsc] node_modules/lit-html/lib/template-result.d.ts(29,27): error TS2304: Cannot find name 'HTMLTemplateElement'.
[tsc] node_modules/lit-html/lib/template.d.ts(64,42): error TS2304: Cannot find name 'Comment'.

Using LitElement

Adding the Web Component Definition

It's time to add some TypeScript code with the first Web Component through LitElement. Let's create a new file src/main.ts with the following content:

import { LitElement, html, customElement, css, property } from 'lit-element';

@customElement('comp-main')
export class CompMain extends LitElement {

    static styles = css`
      :host {
          display: flex;
      }
    `;

    @property({ type: String }) message: string = 'Welcome to LitElement';

    render() {
        return html`
        <div>
            <h1>${this.message}</h1>
            <span>This App uses:</span>
            <ul>
                <li>TypeScript</li>
                <li>Rollup.js</li>
                <li>es-dev-server</li>
            </ul>
        </div>
        `;
    }
} 

The previous code defines a new component for your project. You can use it in your template files as if it were a new member of the HTML vocabulary:

<comp-main></comp-main>

Also, it supports a custom attribute too!

<comp-main message="Welcome to the TypeScript and LitElement world"></comp-main>

Update the index.html File

At this point, the index.html file of the project defines almost the same content as the Web Component. Let's change the <body> content a little bit:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>My Single Page Application</title>
</head>
<body>
    <comp-main></comp-main>
    <script type="module" src="./out-tsc/src/main.js"></script>
</body>
</html>

Two important things are happening now:

  • The <comp-main></comp-main> element will render the brand new component
  • The <script> tag will load the content of the file that contains the component definition.

That's it! You already have a Single-Page Application based on LitElement and TypeScript.

Running the Application

Run the App in Development mode

Serve the application (development mode) using the following command:

npm run start

You should see an output with the host and port:

Web Dev Server started...
[web-dev-server] 
[web-dev-server]   Root dir: /Users/luixaviles/projects/typescript-rollup
[web-dev-server]   Local:    http://localhost:8000/
[web-dev-server]   Network:  http://192.168.1.2:8000/
[web-dev-server] 

The default browser will be opened to display the index.html content.

LitElement App build with TypeScript and Rolluplitelement-app-typescript-rollup

Run the Build Version

First, run the build command:

npm run build

This will generate a dist folder with the following content:

|- dist/
    |- b8c5e46a.js
    |- index.html
    |- ... other scripts

A simple way to serve these files locally would be to use the http-server tool (a command-line http server):

http-server dist/ -o

The output of this command will show you the host and the port you need to use to access the build version of the app:

Starting up http-server, serving dist/
Available on:
  http://127.0.0.1:8080
  http://192.168.1.2:8080
Hit CTRL-C to stop the server
open: http://127.0.0.1:8080

Source Code of the Project

Find the complete project in this GitHub repository: typescript-rollup. Do not forget to give it a star ⭐️ and play around with the code.

Feel free to reach out on Twitter if you have any questions. Follow me on GitHub to see more about my work.

Let's innovate together!

We're ready to be your trusted technical partners in your digital innovation journey.

Whether it's modernization or custom software solutions, our team of experts can guide you through best practices and how to build scalable, performant software that lasts.

Prefer email? hi@thisdot.co