How to Build a LitElement Application with Rollup.js and TypeScript
Tags: typescript, rollup, litelement, webdev
Published: 2021-01-20 04:47:03

In previous posts, I explained [How to setup a TypeScript project using Rollup.js](https://labs.thisdot.co/blog/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](https://labs.thisdot.co/blog/how-to-serve-a-single-page-application-spa-using-rollup-js-and-web-dev) step by step.

In this article, I'll take the result of both tutorials as a starting point to integrate [LitElement](https://lit-element.polymer-project.org/) 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](https://nodejs.org/en/about/releases/).
* **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](https://github.com/luixaviles/typescript-rollup/archive/02-serve-spa.zip) the project seed before adding the new configurations and tools:

```bash
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:

```text
|- 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:

```bash
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](https://lit-html.polymer-project.org/).

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

```json
{
  ...

  "dependencies": {
    "lit-element": "^2.4.0",
    "lit-html": "^1.3.0"
  }
}
```
### Rollup Configuration
The project is already configured and it uses [Rollup](https://rollupjs.org/guide/en/) as the module bundler. Verify the content of `rollup.config.js` as follows:

```js
// 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](https://github.com/open-wc/open-wc/tree/master/packages/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:

```json
{
  ...
  "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](https://modern-web.dev/docs/dev-server/cli-and-configuration/) through a [configuration file](https://modern-web.dev/docs/dev-server/cli-and-configuration/#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:

```json
{
  "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:

```json
{
  "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](https://www.typescriptlang.org/docs/handbook/decorators.html) support for TypeScript. It involves the use of annotations and meta-programming syntax for class declarations and attributes. LitElement [support them](https://lit-element.polymer-project.org/guide/decorators) too. For example:

```ts
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](https://www.typescriptlang.org/docs/handbook/compiler-options.html) 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:

```text
[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:

```ts
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:

```html
<comp-main></comp-main>
```

Also, it supports a custom attribute too!

```html
<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:

```html
<!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:

```bash
npm run start
```

You should see an output with the host and port:

```bash
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](//images.ctfassets.net/zojzzdop0fzx/3Rsy34TAYSVQbzCfI6Vdb2/da86de70c19fb172345d195ca8a95e52/litelement-app-typescript-rollup.png)

### Run the Build Version

First, run the `build` command:

```bash
npm run build
```

This will generate a `dist` folder with the following content:

```text
|- dist/
    |- b8c5e46a.js
    |- index.html
    |- ... other scripts
```
A simple way to serve these files locally would be to use the [http-server](https://www.npmjs.com/package/http-server) tool (a command-line http server):

```bash
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:

```bash
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](https://github.com/luixaviles/typescript-rollup). Do not forget to give it a star ⭐️ and play around with the code.

Feel free to reach out on [Twitter](https://twitter.com/luixaviles) if you have any questions. Follow me on [GitHub](https://github.com/luixaviles) to see more about my work.

---
*This Dot Labs is a modern web consultancy focused on helping companies realize their digital transformation efforts. For expert architectural guidance, training, or consulting in React, Angular, Vue, Web Components, GraphQL, Node, Bazel, or Polymer, visit [thisdotlabs.com](https://www.thisdotlabs.com).*

*This Dot Media is focused on creating an inclusive and educational web for all.  We keep you up to date with advancements in the modern web through events, podcasts, and free content. To learn, visit [thisdot.co](https://www.thisdot.co).*