Skip to content
This repository has been archived by the owner on Feb 28, 2022. It is now read-only.

Commit

Permalink
Merge pull request #53 from adobe/util-vdomino
Browse files Browse the repository at this point in the history
VDOM Util
  • Loading branch information
trieloff authored Oct 1, 2018
2 parents 07a360a + 82b34fe commit 3b1dcab
Show file tree
Hide file tree
Showing 12 changed files with 1,911 additions and 233 deletions.
172 changes: 170 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,10 +151,30 @@ Following main properties exist:
- `type`: the content type of the document
- `image`: the URL of the first image in the document
- `htast`: the HTML AST
- `sections[]`: The main sections of the document, as an enhanced MDAST
- `document`: a DOM-compatible [`Document`](https://developer.mozilla.org/en-US/docs/Web/API/Document) representation of the (HTML) document ([see below](#contentdocument-in-detail))
- `sections[]`: The main sections of the document, as an enhanced MDAST ([see below](#contentsections-in-detail))
- `html`: a string of the content rendered as HTML
- `children`: an array of top-level elements of the HTML-rendered content

### `content.document` in Detail

For developers that prefer using the rendered HTML over the input Markdown AST, `content.document` provides a representation of the rendered HTML that is API-compatible to the `window.document` object you would find in a browser.

The most common way of using it is probably calling `content.document.innerHTML`, which gives the full HTML of the page, but other functions like

- `content.document.getElementsByClassName`
- `content.document.querySelector`
- `content.document.querySelectorAll`

are also available. Please note that some functions like

- `content.document.getElementsByClassName`
- `content.document.getElementByID`

are less useful because the HTML generated by the default pipeline does not inject class name or ID attributes.

The tooling for generating (Virtual) DOM nodes from Markdown AST is made available as a utility class, so that it can be used in custom `pre.js` scripts, and [described below](#generate-a-virtual-dom-with-utilsvdom).

### `content.sections` in Detail

The default pipeline extracts sections from a Markdown document, using both "thematic breaks" like `***` or `---` and embedded YAML blocks as section markers. If no sections can be found in the document, the entire `content.mdast` will be identically to `content.sections[0]`.
Expand Down Expand Up @@ -198,6 +218,154 @@ The only known property in `error` is

## Utilities

### Generate a Virtual DOM with `utils.vdom`

`VDOM` is a helper class that transforms [MDAST](https://github.com/syntax-tree/mdast) Markdown into DOM nodes using customizable matcher functions or expressions.

It can be used in scenarios where:

- you need to represent only a `section` of the document in HTML
- you have made changes to `content.mdast` and want them reflected in HTML
- you want to customize the HTML output for certain Markdown elements

#### Getting Started

Load the `VDOM` helper through:

```javascript
const VDOM = require('@adobe/hypermedia-pipeline').utils.vdom;
```

#### Simple Transformations

```javascript
content.document = new VDOM(content.mdast).getDocument();
```

This replaces `content.document` with a re-rendered representation of the Markdown AST. It can be used when changes to `content.mdast` have been made.

```javascript
content.document = new VDOM(content.sections[0]).getDocument();
```

This uses only the content of the first section to render the document.

#### Matching Nodes

Nodes in the Markdown AST can be matched in two ways: either using a [select](https://www.npmjs.com/package/unist-util-select)-statement or using a predicate function.

```javascript
const vdom = new VDOM(content.mdast);
vdom.match('heading', () => '<h1>This text replaces your heading</h1>');
content.document = vdom.getDocument();
```

Every node with the type `heading` will be rendered as `<h1>This text replaces your heading</h1>`;

```javascript
const vdom = new VDOM(content.mdast);
vdom.match(function test(node) {
return node.type === 'heading';
}, () => '<h1>This text replaces your heading</h1>');
content.document = vdom.getDocument();
```

Instead of the select-statement, you can also provide a function that returns `true` or `false`. The two examples above will have the same behavior.

#### Creating DOM Nodes

The second argument to `match` is a node-generating function that should return one of the following three options:

1. an [HAST](https://github.com/syntax-tree/hast) (Hypertext Abstract Syntax Tree) node
2. a DOM [Node](https://developer.mozilla.org/en-US/docs/Web/API/Node)
3. a `String` containing HTML tags.

```javascript
vdom.match('link', (_, node) => {
return {
type: 'element',
tagName: 'a',
properties: {
href: node.url,
rel: 'nofollow'
},
children: [
{
type: 'text',
value: node.children.map(({ value }) => value)
}
]
}
}
```
Above: injecting `rel="nofollow"` using HTAST.
```javascript
const h = require('hyperscript');

vdom.match('link', (_, node) => h(
'a',
{ href: node.url, rel: 'nofollow' },
node.children.map(({ value }) => value),
);
```
Above: doing the same using [Hyperscript](https://github.com/hyperhype/hyperscript) (which creates DOM elements) is notably shorter.
```javascript
vdom.match('link', (_, node) =>
`<a href="${node.url}" rel="nofollow">$(node.children.map(({ value }) => value)).join('')</a>`;
```
Above: Plain `String`s can be constructed using String Templates in ES6 for the same result.
#### Creating Responsive Images
The VDOM Utility is prepared to create `srcset` and `sizes` attributes for responsive images. By default, five different resolutions ranging from `480w` to `4096w` will be generated. To create truly effective responsive images, some knowledge of the desired layout of the page, and hence some configuration is required.
`utils.vdom` provides two configuration options:
1. Define what physical image widths are made available with `widths`
2. Define which images get loaded with `sizes`
Both configuration options get passed to an optional `options` argument for the `VDOM` constructor:
```javascript
const widths;
const sizes;
content.document = new VDOM(content.mdast, {widths, sizes}).getDocument();
```
`widths` is either an array of possible image widths (positive integer values) or a `widthpec` that looks like this:
```javascript
const widths = {
from: 320,
to: 9600,
steps: 10
};
const sizes;
content.document = new VDOM(content.mdast, {widths, sizes}).getDocument();
```
Responsive images will be generated on the fly and only when requested, so the only cost involved with increasing the number of `steps` is the length of the resultant `srcset` attribute.
In order to define what images get loaded, the `sizes` attribute must be set. In HTML, sizes is a comma-separated list of pairs of media queries and length expressions. For `util.vdom`, the setting is an array of these pairs.
```javascript
const widths;
const sizes = [
'(min-width: 36em) 33.3vw',
'(min-width: 48em) calc(.333 * (100vw - 12em))',
'100vw'
];
content.document = new VDOM(content.mdast, {widths, sizes}).getDocument();
```
This gives you fine-grained control over the image widths that are made available and will get loaded by browsers based on the width of the browser window. With `util.vdom` you can have different settings per page- or section-type.
### Infer Content Types with `utils.types`
In addition to the automatically inferred content types for each section, `utils.types` provides a `TypeMatcher` utility class that allows matching section content against a simple expression language and thus enrich the `section[].types` values.
Expand All @@ -221,4 +389,4 @@ In the example above, all sections that have a `heading` as the first child will
* `heading? image` – an optional `heading` followed by one `image`
* `heading paragraph* image` – a `heading` followed by any number of `paragraph`s (also no paragraphs at all), followed by an `image`
* `(paragraph|list)` – a `paragraph` or a `list`
* `^heading (image paragraph)+$` – one `heading`, followed by pairs of `image` and `paragraph`, but at least one
* `^heading (image paragraph)+$` – one `heading`, followed by pairs of `image` and `paragraph`, but at least one
Loading

0 comments on commit 3b1dcab

Please sign in to comment.