Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/bcosca/undertone

A Custom Elements (v1-spec) code organizer/compiler that tones down Javascript by emphasizing the structure and beauty of HTML
https://github.com/bcosca/undertone

custom-elements-v1 declarative es6-javascript frontend html5 javascript undertone web-components

Last synced: 3 days ago
JSON representation

A Custom Elements (v1-spec) code organizer/compiler that tones down Javascript by emphasizing the structure and beauty of HTML

Awesome Lists containing this project

README

        

![Logo](https://github.com/bcosca/undertone/raw/master/logo.png)

A [Custom Elements (v1-spec)](https://html.spec.whatwg.org/multipage/custom-elements.html) code organizer/compiler that tones down Javascript by emphasizing the structure and beauty of HTML.

![GitHub package.json version](https://img.shields.io/github/package-json/v/bcosca/undertone) [![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](//standardjs.com) ![GitHub](https://img.shields.io/github/license/bcosca/undertone?color=blue)

## Installation

```shell
npm install --save-dev undertone
```

## How it works

A custom element definition is nothing more than a self-contained HTML fragment with a custom element tag name that wraps its content, appearance and behavior. Definitions may contain any mix of text, HTML tags, including **``**, **`<style>`**, **`<link rel="stylesheet">`** and **`<slot>`** tags.

```
<planet-mars>
<div>${message}</div>
<script>
const message = 'Hello, world'

```

The compiler rebuilds the above structure representing the source code into a reusable Web component that works on any browser aligned with the ES6+ Custom Elements v1 language specs. The following generated code is an approximate translation:

```
customElements.define(
'planet-mars',
class extends HTMLElement {
constructor () {
super()
this.attachShadow({ mode: 'open' })
}
connectedCallback () {
const message = 'Hello, world'
let template = document.createElement('template')
template.innerHTML = `

${message}
`
this.shadowRoot.appendChild(template.content)
}
}
)
```

Modern and fairly recent mobile and desktop browsers already have native Javascript ES6+ with Custom Elements capability, so polyfills to augment browser capabilities are unnecessary.

See which browsers support these features here:

* [Javascript ES6+](https://caniuse.com/#search=ecmascript%202015)
* [Custom Elements v1 language spec](https://caniuse.com/#feat=custom-elementsv1)

## Features

* Clear separation of form and function (content and behavior)
* Simple format, declarative style: regular HTML5 sprinkled with CSS and Javascript
* Easy to read: IDEs/editors already provide syntax highlighting for HTML
* Generation of source maps to help with debugging

## Putting it all together

Basic document layout

```
./index.html









```

Example of a component

```
./html/x-foo.html


console.log('Hello, world')

```

Use the command line tool to compile the component's source code to Javascript and save it to a file.

```
undertone ./html/x-foo.html -m inline -o ./js/app.js
```

## CLI

```plaintext
USAGE: undertone [options] input-file ...

Basic options:
-o, --output output-file Target file (default: stdout)
-m, --map sourcemap-file Generate source map (default: none)
-h, --help Usage information

Advanced options (see documentation):
-c, --class class-file DOM interface (default: undertone.js)
-g, --global Enable global styles (default: scoped styles)
--no-preamble Exclude preamble from output
```

### Options

**`-o, --output output-file`**

The name of the generated Javascript file. If omitted, output is sent to **`stdout`**.

**`-m, --map sourcemap-file`**

The name of the source map linked to the output file.

* If the special **`inline`** keyword is supplied as an argument, the source map is generated along with the output.

* Any other value supplied as argument to **`--map`** is treated as a file name.

**`-c, --class class-file`**

The name of the base class that the compiled components will inherit from. By default, **`undertone.js`** generates custom elements that extend the `Undertone` class (contained in `src/undertone.js`). This DOM interface is responsible for rewiring the v1-spec to the [custom element definitions](#how-it-works). Review the source to gain more info before attempting to override it.

**`-g, --global`**

Custom elements do not normally inherit from the document's global stylesheets and style tags by default. Enable this flag to override this behavior.

**`--no-preamble`**

Depending on your build process, you may choose to defer or not include the `Undertone` class in the generated output. Enable this flag to inform the compiler that `src/undertone.js` should not be prepended to the output.

## Component structure and definition

A custom element definition must have a [valid](https://html.spec.whatwg.org/multipage/custom-elements.html#valid-custom-element-name) tag name and it must be declared as a top level HTML element in the source file.

#### Example

```

```

> You may have several top level custom elements in one file, although one custom element definition per file is recommended.

#### Anatomy of a component

A custom element definition may contain a mix of HTML and secondary level **``**, **`<link rel="stylesheet">`** and **`<script>`** tags that work in harmony to define its overall appearance and behavior. Everything inside a custom element definition is optional.

```
<my-widget>
<link rel="stylesheet" href="stylesheet.css">
<style light-dom>
/* Light DOM styles */


/* Shadow root styles */




/* Component-specific Javascript code */

```

Custom elements may employ an external stylesheet by simply inserting a **``** tag in the element definition. Styles declared in external stylesheets affect the light DOM.

Styles defined within **``** tags affect the corresponding CSS properties of either light DOM or shadow root elements, depending on whether the **`light-dom`** attribute is present or not.

**`<slot>`** tags that serve as placeholders for external content may be employed anywhere within the custom element's markup. More information about **`<slot>`** elements is available [here](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/Slot).

The presence of an **`observe`** attribute in a custom element declaration tag (e.g. **`<my-widget observe="size">`**) provides a hint to the compiler that the attribute specified should be observed throughout the life span of the element and any change to its value triggers a corresponding component update.

Multiple attributes can be observed by specifying a comma- or space-delimited string representing the attributes to be observed.

## Undertone class

Code generated by the compiler automatically inherits from the [Undertone class](src/undertone.js) by default. This Javascript class serves as a thin wrapper around the standard **`HTMLElement`** that all custom elements derive from. At compilation time, this class is inserted into the aggregated output as a preamble to satisfy code dependencies.

Under the hood, this little class provides the following services:

* A component caching mechanism to optimize rendering performance
* Automatic synchronization of element attributes and properties
* Redirection of basic v1-spec lifecycle callbacks to synthetic events that can be handled by code inside the component's **`<script>`** tags
* A **`refs`** object for obtaining references to shadow DOM elements
* A compile-time option for scoped or global styles

> The Undertone class is a minimal implementation of a v1-spec DOM interface that provides these services. Being neither a platform nor a framework, it does not enforce any rigid methodology. The philosophy behind Undertone: No overengineered and no opinionated stuff.

### Events

Undertone events are directly mapped to basic [v1-spec](https://html.spec.whatwg.org/multipage/custom-elements.html) lifecycle callbacks of custom elements. These synthetic events are prefixed with a colon `(:)` to make them easily identifiable.

#### :connect

```
this.addEventListener(':connect', () => {
})
```

The **`:connect`** event handler is called each time the custom element is inserted into the DOM. Its primary job is to prepare the element for subsequent display. When its task is done, the Undertone class assembles the markup inside the component definition and renders the content in the [shadow DOM](https://developers.google.com/web/fundamentals/web-components/shadowdom).

This event fires when the browser's native **`connectedCallBack`** of a custom element is triggered.

#### :ready

```
this.addEventListener(':ready', () => {
})
```

This event occurs when the Web browser is done rendering the component's HTML content. The event handler is responsible for visual tweaks made by changing element properties that are not available before rendering.

#### :update

```
this.addEventListener(':update', () => {
})
```

The **`:update`** event is wired to the native **`attributeChangedCallBack`** method. It fires when the custom element's observed attributes change during its lifecycle.

#### :disconnect

```
this.addEventListener(':disconnect', () => {
})
```

The **`:disconnect`** event maps to the custom element's native **`disconnectedCallBack`** method.

### Compiler directives

#### %if

The **`%if`** directive renders a block if the specified condition is true.

```
<my-bar>
%if (age >= 18):
<span class="green">Have a drink</span>
%end
<script>
const age = 18
</script>
</my-bar>
```

An optional **`%else`** directive may be inserted to provide a fallback mechanism if needed.

```
<my-bar>
%if (age > 18):
<span class="green">Have a drink</span>
%else if (age === 18):
<span class="orange">ID please</span>
%else:
<span class="red">Go away!</span>
%end
<script>
const age = 18
</script>
</my-bar>
```

#### %each

Use the **`each`** statement to iterate over a list.

```
<my-list>
<ul>
%each (item in items):
<li>${item}</li>
%end
</ul>
<script>
const items = [ 'Budweiser', 'Heineken', 'Skol' ]
</script>
</my-list>
```

## Contributing

Before you submit a pull request, please check the code base and past issues to avoid duplication.

In addition to improving the project, refactoring code, and implementing features, this project welcomes the following types of contributions:

* Ideas: Participate in an issue thread or start your own to have your voice heard
* Writing: Contribute your expertise in an area by helping expand the documentation
* Copy editing: Fix typos, clarify language, and generally improve the quality of the content
* Formatting: Help keep content easy to read with consistent formatting

#### Testing your code

Please run **`npm test`** to ensure all tests are passing before submitting a pull request - unless you're creating a failing test to increase test coverage or show a problem.

## Support

Help support further development and maintenance of the **undertone.js** project.

[![Become a Patron!](https://c5.patreon.com/external/logo/become_a_patron_button.png)](https://www.patreon.com/bePatron?u=20757210)

## License

**undertone.js** is released under the [MIT License](./LICENSE.md)