Building Glimmer apps with Web Dev Server
Tags: glimmer, glimmerjs, webdevserver
Published: 2020-12-14 05:45:15

# Intro
This is the third post in the series of blog posts describing about building [Glimmer.js](https://glimmerjs.com) apps with general purpose bundlers. The previous posts walked you through building Glimmer apps with [Snowpack](https://snowpack.dev) and [Rollup](https://rollupjs.org). You can check them out here.

With Snowpack
{% post rajasegar/building-glimmer-apps-with-snowpack-igc %}

With Rollup
{% post rajasegar/building-glimmer-apps-with-rollup-3jcj %}

In this post, we are going to build our Glimmer.js apps with a build tool called [Web Dev Server](https://modern-web.dev/docs/dev-server/overview/)

# What is Web Dev Server?
`Web Dev Server` helps developing for the web, using native browser features like [ES modules](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules). It is powered by [esbuild](https://github.com/evanw/esbuild) and [Rollup](https://rollupjs.org). esbuild is an extremely fast JavaScript bundler and minifier written in [Go](https://golang.org/). 

I came across this tool from [Modern Web Dev](https://modern-web.dev) which is an awesome project from the creators or [Open Web Components](https://open-wc.org/), with a goal to provide developers with the guides and tools they need to build for the modern web. They enable developers to work closely with the browser and avoid complex abstractions.

Web Dev Server is ideal for [buildless](https://buildless.site/) workflows, and has a plugin architecture for light code transformations. It has got many features like efficient browser caching for fast reloads, compatibility with older browsers, auto-reload on file changes, History API fallback for Single Page Apps(SPA) routing, plugins and middleware API for non-JS files.

# Setting up the project
```
mkdir glimmer-web-dev-server
cd glimmer-web-dev-server
npm init -y
```
Adding `src` directory for source files:
```
mkdir src
cd src
touch App.js App.css
```

### App.js
```js
import Component, { hbs } from '@glimmerx/component';

import logo from './logo.svg';
import styles from './App.css';

export default class App extends Component {

  constructor() {
    super(...arguments);
    this.styles = styles;
    this.logo = logo;
  }

  static template = hbs`
    <div id="intro" class={{this.styles.intro}}>
      <img src={{this.logo}}/>
      <h1>Hello World, glimmerx!</h1>
      <h3>
        you can get started by editing <code>src/App.js</code></a>
      </h3>
    </div>
  `;
}
```
Please note that we are going to use CSS Modules for our component with PostCSS. We will discuss about this later.

### App.css
```css
.intro {
  width: 34em;
}

.intro h1, .intro h3 {
  font-family: Roboto, 'Helvetica Neue', Helvetica, Arial, sans-serif;
  font-style: italic;
  color: #FFFFFF;
  margin: 0.35em;
}

.intro img {
  float: left;
  width: 6.5em;
  margin: 0.5em 2em;
}

.intro a {
  color: #FFFFFF;
}
```
Adding `demo` directory for index.html and main JS file:
```
mkdir demo
cd demo
touch index.html index.js
```

Next we create our `index.html` file with the following markup, we need a mount-point for our main component to load in the DOM and we need to include the `index.js` file as a module with `<script type="module">`.

### index.html
```js
<!DOCTYPE html>
<html>
  <head>
    <title>GlimmerX with web-dev-server</title>
  </head>
  <body>
    <div id="app"></div>
    <script type="module" src="./index.js"></script>
  </body>
</html>
```
Now the main index.js file will render our `App` component in the DOM node `#app`.

### index.js
```js
import { renderComponent } from '@glimmerx/core';
import App from "../src/App.js";

renderComponent(App, document.getElementById('app'));
```
# Adding dependencies
We need to install the `@glimmerx` libraries for creating and rendering our Glimmer components.

```
yarn add @glimmerx/component @glimmerx/core
```

# Adding devDependencies
Adding @web/dev-server dependencies
```
yarn add -D @web/dev-server @web/dev-server-esbuild
```

## Configuring @web/dev-server
Now, it's time to create the configuration file. Web Dev Server looks for a configuration file in the current working directory called `web-dev-server.config.*`

The file extension can be `.js`, `.cjs` or `.mjs`. A `.js` file will be loaded as an ES module or Common JS module based on your version of node, and the package type of your project.

The Web Dev Server core team recommends writing the configuration using Node.js ES module syntax and using the `.mjs` file extension to make sure your config is always loaded correctly.

```
touch web-dev-server.config.mjs
``` 

### web-dev-server.config.mjs
```js
import { esbuildPlugin } from "@web/dev-server-esbuild";

export default {
  nodeResolve: true,
  plugins: [],
};
```

Let's start building our configuration step-by-step. First we need to compile our JS from the component source files. We are going to use Rollup and Babel for the same. Let's install the required packages for rollup and babel plugins.

For babel
```
yarn add -D @babel/plugin-proposal-class-properties @babel/plugin-proposal-decorators @babel/preset-env
```
For compiling our Glimmer components with the inline handlebars syntax we need to install the `@glimmerx/babel-plugin-component-templates`, about which you can know more in [here](https://github.com/glimmerjs/glimmer-experimental).

```
yarn add -D @glimmerx/babel-plugin-component-templates
```

We need an Adapter for [using rollup plugins](https://modern-web.dev/docs/dev-server/plugins/rollup/) in Web Dev Server. Web Dev Server plugins and rollup plugins share a very similar API, making it possible to reuse rollup plugins inside Web Dev Server with an adapter.

For Rollup we need
```
yarn add -D @web/dev-server-rollup @rollup/plugin-babel
```

Let's add our babel plugins to the config file.
```js
import { esbuildPlugin } from "@web/dev-server-esbuild";
import { fromRollup } from '@web/dev-server-rollup';
import * as babelModule from '@rollup/plugin-babel';

const { babel } = babelModule;
const babelPlugin = fromRollup(babel);

export default {
  nodeResolve: true,
  plugins: [
    babelPlugin({
      babelHelpers: 'bundled',
      plugins: [
        '@glimmerx/babel-plugin-component-templates',
        ['@babel/plugin-proposal-decorators', { legacy: true }],
        '@babel/plugin-proposal-class-properties',
      ],
    }),
  ],
};
```

We are importing the babel rollup plugin and the `fromRollup` function in our configuration file. Then, we are wrapping the rollup plugin with the adapter function to make it work.

Now it's time to add our scripts in the package.json to start our dev server and build our source files.

```json
"start": "web-dev-server --open demo/ --node-resolve --watch"
```
What we are doing here is to tell the web-dev-server to open and watch the `demo` folder for file changes and use the `--node-resolve` flag to resolve bare module imports for use in the browser.

So now you can start the web-dev-server to load our app in the browser by issuing the following command:
```
yarn start
```

And if you go to the url `http://localhost:8000/demo` in your browser, you probably get a blank page and some errors in the console. The reason being that we have not yet configured our web-dev-server to process the CSS and images since the build process assumes that any imported files are meant to be compiled to JS, Web Dev Server serves many different kinds of files to the browser. If you are transforming a non-standard filetype to JS, for example `.css` or `.svg` files, you need to instruct the server to handle it as a JS file.

Let's handle them now using our Rollup plugins `rollup-plugin-postcss` and `@rollup/plugin-image`.

```
yarn add -D @rollup/plugin-image rollup-plugin-postcss
```
This allows us to use the PostCSS [CSS Modules](https://github.com/css-modules/css-modules) feature which we discussed earlier. With that we don't have to worry about style clashes and naming our css classes and selectors since all of them will be properly scoped and encapsulated. We can import our style definitions in our components and use them like:

```js
import styles from './App.css';
...
<div class={{styles.intro}}/>
```

And now, add these plugins for loading css and images to our config.

```js
...
import postcss from 'rollup-plugin-postcss';
import image from '@rollup/plugin-image';
...
const postcssPlugin = fromRollup(postcss);
const imagePlugin = fromRollup(image);

export default {
   ...
  plugins: [
    ...
    postcssPlugin({
      include: ['src/**/*.css'], 
      modules: true
    }),
    imagePlugin()
  ],
  mimeTypes: {
    '**/*.css': 'js',
    '**/*.svg': 'js'
  }
};
```

In the above config, we are telling the `rollup-plugin-postcss` to include all the css files inside the src folder and use CSS modules with the modules option set to true. 

And we also need to tell web-dev-server to load all the `css` and `svg` files as JS with the `mimeTypes` option.

Now this is how the full and final config for our web-dev-server looks like.
```js
import { esbuildPlugin } from "@web/dev-server-esbuild";
import { fromRollup } from '@web/dev-server-rollup';
import * as babelModule from '@rollup/plugin-babel';
import postcss from 'rollup-plugin-postcss';
import image from '@rollup/plugin-image';

const { babel } = babelModule;
const babelPlugin = fromRollup(babel);
const postcssPlugin = fromRollup(postcss);
const imagePlugin = fromRollup(image);

export default {
  nodeResolve: true,
  plugins: [
    babelPlugin({
      babelHelpers: 'bundled',
      plugins: [
        '@glimmerx/babel-plugin-component-templates',
        ['@babel/plugin-proposal-decorators', { legacy: true }],
        '@babel/plugin-proposal-class-properties',
      ],
    }),
    postcssPlugin({
      include: ['src/**/*.css'], 
      modules: true
    }),
    imagePlugin()
  ],
  mimeTypes: {
    '**/*.css': 'js',
    '**/*.svg': 'js'
  }
};
```

Now restart our server and visit `http://localhost:8000/demo` url in the browser. Your app should look like the this.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/62ythu25fmoy3s08mrbg.png)

# Source code
The source code for this post can be found in this [Github](https://github.com/rajasegar/glimmer-web-dev-server) repository. The repository also contains some bonus stuff of writing tests for your Glimmer components and running tests with [Web Test Runner](https://modern-web.dev/docs/test-runner/overview/) another awesome tool from the Modern Web project.

This concludes our Building Glimmer apps series with different bundlers. Please let me know in the comments if you have any queries or feedback, I will be glad to answer the same.