Skip to content

Commit

Permalink
feat: Add synchronous tags: htmlSync(), htmlFragmentSync(), close #50
Browse files Browse the repository at this point in the history
  • Loading branch information
motss committed Mar 16, 2020
1 parent a8002e8 commit ccf2e0f
Show file tree
Hide file tree
Showing 8 changed files with 255 additions and 46 deletions.
84 changes: 45 additions & 39 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,20 +38,20 @@ This module also gets featured in [web-padawan/awesome-lit-html][web-padawan-awe
- [Visual Studio Code](#visual-studio-code)
- [Install](#install)
- [Usage](#usage)
- [TypeScript or native ES Modules](#typescript-or-native-es-modules)
- [html()](#html)
- [htmlFragment()](#htmlfragment)
- [Node.js](#nodejs)
- [html()](#html-1)
- [htmlFragment()](#htmlfragment-1)
- [SSR with Express (Node.js)](#ssr-with-express-nodejs)
- [html()](#html)
- [htmlSync()](#htmlsync)
- [htmlFragment()](#htmlfragment)
- [htmlFragmentSync()](#htmlfragmentsync)
- [SSR with Express (Node.js)](#ssr-with-express-nodejs)
- [Browser](#browser)
- [ES Modules](#es-modules)
- [UMD](#umd)
- [deno](#deno)
- [API Reference](#api-reference)
- [html()](#html-2)
- [htmlFragment()](#htmlfragment-2)
- [html()](#html-1)
- [htmlSync()](#htmlsync-1)
- [htmlFragment()](#htmlfragment-1)
- [htmlFragmentSync()](#htmlfragmentsync-1)
- [deno](#deno)
- [License](#license)

## Features
Expand Down Expand Up @@ -83,11 +83,10 @@ $ npm install lit-ntml

## Usage

### TypeScript or native ES Modules

#### html()
### html()

```ts
// const { html } = require('lit-ntml'); // CommonJS import style
import { html } from 'lit-ntml';

const peopleList = ['Cash Black', 'Vict Fisherman'];
Expand All @@ -99,48 +98,47 @@ const asyncListTask = async () => `<ul>${peopleList.map(n => `<li>${n}</li>`)}</
await html`${syncTask}${asyncLiteral}${asyncListTask}`; /** <!DOCTYPE html><html><head></head><body><h1>Hello, World!</h1><h2>John Doe</h2><ul><li>Cash Black</li><li>Vict Fisherman</li></ul></body></html> */
```

#### htmlFragment()
### htmlSync()

```ts
import { htmlFragment as html } from 'lit-ntml';
// const { htmlSync } = require('lit-ntml'); // CommonJS import style
import { htmlSync as html } from 'lit-ntml';

const peopleList = ['Cash Black', 'Vict Fisherman'];
const syncTask = () => `<h1>Hello, World!</h1>`;
const externalStyleLiteral = `<style>body { margin: 0; padding: 0; box-sizing: border-box; }</style>`;

/** Assuming top-level await is enabled... */
await html`${externalStyleLiteral}${syncTask}`; /** <style>body { margin: 0; padding: 0; box-sizing: border-box; }</style><h1>Hello, World!</h1> */
html`${syncTask}${peopleList}`;
/** <!DOCTYPE html><html><head></head><body><h1>Hello, World!</h1>Cash BlackVictFisherman[object Promise]</body></html> */
```

### Node.js

#### html()
### htmlFragment()

```js
const { html } = require('lit-ntml');
```ts
// const { htmlFragment as html } = require('lit-ntml'); // CommonJS import style
import { htmlFragment as html } from 'lit-ntml';

const peopleList = ['Cash Black', 'Vict Fisherman'];
const syncTask = () => `<h1>Hello, World!</h1>`;
const asyncLiteral = Promise.resolve('<h2>John Doe</h2>');
const asyncListTask = async () => `<ul>${peopleList.map(n => `<li>${n}</li>`)}</ul>`;
const externalStyleLiteral = `<style>body { margin: 0; padding: 0; box-sizing: border-box; }</style>`;

/** Assuming top-level await is enabled... */
await html`${syncTask}${asyncLiteral}${asyncListTask}`; /** <!DOCTYPE html><html><head></head><body><h1>Hello, World!</h1><h2>John Doe</h2><ul><li>Cash Black</li><li>Vict Fisherman</li></ul></body></html> */
await html`${externalStyleLiteral}${syncTask}`; /** <style>body { margin: 0; padding: 0; box-sizing: border-box; }</style><h1>Hello, World!</h1> */
```

#### htmlFragment()
### htmlFragmentSync()

```js
const { htmlFragment } = require('lit-ntml');
```ts
// const { htmlFragmentSync as html } = require('lit-ntml'); // CommonJS import style
import { htmlFragmentSync as html } from ('lit-ntml';

const html = htmlFragment;
const peopleList = ['Cash Black', 'Vict Fisherman'];
const syncTask = () => `<h1>Hello, World!</h1>`;
const externalStyleLiteral = `<style>body { margin: 0; padding: 0; box-sizing: border-box; }</style>`;
const asyncTask = Promise.resolve(1);

/** Assuming top-level await is enabled... */
await html`${externalStyleLiteral}${syncTask}`; /** <style>body { margin: 0; padding: 0; box-sizing: border-box; }</style><h1>Hello, World!</h1> */
html`${syncTask}${peopleList}${asyncTask}`;
/** <h1>Hello, World!</h1>Cash BlackVictFisherman[object Promise] */
```

#### SSR with Express (Node.js)
### SSR with Express (Node.js)

[![Edit SSR with Express and LitNtml](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/ssr-with-express-and-litntml-4tbv9?fontsize=14)

Expand Down Expand Up @@ -177,20 +175,28 @@ await html`${externalStyleLiteral}${syncTask}`; /** <style>body { margin: 0; pad
</html>
```

## deno

👉 Check out the [deno][] module at [deno_mod/lit_ntml][].

## API Reference

### html()

- returns: <[Promise][promise-mdn-url]&lt;[string][string-mdn-url]&gt;> Promise which resolves with rendered HTML document string.

### htmlSync()

This method works the same as `html()` except that this is the synchronous version.

### htmlFragment()

- returns: <[Promise][promise-mdn-url]&lt;[string][string-mdn-url]&gt;> Promise which resolves with rendered HTML document fragment string.

### htmlFragmentSync()

This method works the same as `htmlFragment()` except that this is the synchronous version.

## deno

👉 Check out the [deno] module at [deno_mod/lit_ntml].

## License

[MIT License](https://motss.mit-license.org) © Rong Sen Ng
Expand Down
18 changes: 15 additions & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import {
parseFragment,
serialize,
} from 'parse5';
import { parsePartial } from './parse-literals.js';
import { parseLiteralsSync } from './parse-literals-sync.js';
import { parseLiterals } from './parse-literals.js';

// export const DEFAULT_MINIFY_OPTIONS: htmlMinifier.Options = {
// collapseBooleanAttributes: true,
Expand All @@ -22,11 +23,22 @@ import { parsePartial } from './parse-literals.js';
// trimCustomFragments: true,
// };

const parser = parsePartial(serialize);
const parser = parseLiterals(serialize);
const parserSync = parseLiteralsSync(serialize);

export const html = async (s: TemplateStringsArray, ...e: any[]) =>
parser(c => parse(`<!doctype html>${c}`), s, ...e);
export const htmlFragment = async (s: TemplateStringsArray, ...e: any[]) =>
parser(parseFragment, s, ...e);

export default { html, htmlFragment };
export const htmlSync = (s: TemplateStringsArray, ...e: any[]) =>
parserSync(c => parse(`<!doctype html>${c}`), s, ...e);
export const htmlFragmentSync = (s: TemplateStringsArray, ...e: any[]) =>
parserSync(parseFragment, s, ...e);

export default {
html,
htmlFragment,
htmlFragmentSync,
htmlSync,
};
18 changes: 15 additions & 3 deletions src/lit-ntml.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import {
parseFragment,
serialize,
} from 'nodemod/dist/lib/parse5.js';
import { parsePartial } from './parse-literals.js';
import { parseLiteralsSync } from './parse-literals-sync.js';
import { parseLiterals } from './parse-literals.js';

// export const DEFAULT_MINIFY_OPTIONS: htmlMinifier.Options = {
// collapseBooleanAttributes: true,
Expand All @@ -22,11 +23,22 @@ import { parsePartial } from './parse-literals.js';
// trimCustomFragments: true,
// };

const parser = parsePartial(serialize);
const parser = parseLiterals(serialize);
const parserSync = parseLiteralsSync(serialize);

export const html = async (s: TemplateStringsArray, ...e: any[]) =>
parser(c => parse(`<!doctype html>${c}`), s, ...e);
export const htmlFragment = async (s: TemplateStringsArray, ...e: any[]) =>
parser(parseFragment, s, ...e);

export default { html, htmlFragment };
export const htmlSync = (s: TemplateStringsArray, ...e: any[]) =>
parserSync(c => parse(`<!doctype html>${c}`), s, ...e);
export const htmlFragmentSync = (s: TemplateStringsArray, ...e: any[]) =>
parserSync(parseFragment, s, ...e);

export default {
html,
htmlFragment,
htmlFragmentSync,
htmlSync,
};
17 changes: 17 additions & 0 deletions src/parse-literals-sync.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import type {
parse,
parseFragment,
serialize,
} from 'parse5';
import { processLiteralsSync } from './process-literals-sync.js';

export function parseLiteralsSync(serializeFn: typeof serialize) {
return (
fn: typeof parse | typeof parseFragment,
strings: TemplateStringsArray,
...exps: any[]
) => {
const content = processLiteralsSync(strings, ...exps);
return serializeFn(fn(content));
};
}
2 changes: 1 addition & 1 deletion src/parse-literals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import type {
} from 'parse5';
import { processLiterals } from './process-literals.js';

export function parsePartial(serializeFn: typeof serialize) {
export function parseLiterals(serializeFn: typeof serialize) {
return async (
fn: typeof parse | typeof parseFragment,
strings: TemplateStringsArray,
Expand Down
15 changes: 15 additions & 0 deletions src/process-literals-sync.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export function processLiteralsSync(
strings: TemplateStringsArray,
...exps: any[]
): string {
const done = exps.map((n) => {
return (Array.isArray(n) ? n : [n]).map(o => 'function' === typeof(o) ? o() : o);
});
const doneLen = done.length;

return strings.reduce((p, n, i) => {
const nTask = done[i] ;
const joined = Array.isArray(nTask) ? nTask.join('') : nTask;
return `${p}${i >= doneLen ? n : `${n}${joined}`}`;
}, '');
}
79 changes: 79 additions & 0 deletions src/test/html-fragment-sync.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { htmlFragmentSync as html } from '..';

const helloWorld = `<h1>Hello, World!</h1>`;
const peopleList = [
'John Doe',
'Michael CEO',
'Vict Fisherman',
'Cash Black',
];

it(`throws when error happens`, () => {
try {
// tslint:disable-next-line: no-unused-expression
html`${() => { throw new Error('error'); }}`;
} catch (e) {
expect(e).toStrictEqual(new Error('error'));
}
});

it(`renders`, () => {
const d = html`${helloWorld}`;

expect(d).toStrictEqual(
`<h1>Hello, World!</h1>`
);
});

it(`renders with sync tasks`, () => {
const syncTask = () => helloWorld;
const syncLiteral = 'John Doe';
const d = html`<section>${syncTask}<h2>${syncLiteral}</h2></section>`;

expect(d).toStrictEqual(
// tslint:disable-next-line: max-line-length
`<section><h1>Hello, World!</h1><h2>John Doe</h2></section>`
);
});

it(`renders with async tasks`, () => {
const asyncTask = async () => helloWorld;
const asyncLiteral = Promise.resolve('John Doe');
const d = html`<section>${asyncTask}<h2>${asyncLiteral}</h2></section>`;

expect(d).toStrictEqual(
// tslint:disable-next-line: max-line-length
`<section>[object Promise]<h2>[object Promise]</h2></section>`
);
});

it(`renders with a mixture of sync + async tasks`, async () => {
const asyncTask = () => helloWorld;
const syncLiteral = await 'John Doe';
const d = html`<section>${asyncTask}<h2>${syncLiteral}</h2></section>`;

expect(d).toStrictEqual(
// tslint:disable-next-line: max-line-length
`<section><h1>Hello, World!</h1><h2>John Doe</h2></section>`
);
});

it(`renders a list of sync tasks`, () => {
const d = html`${helloWorld}<ul>${peopleList.map(n => `<li>${n}</li>`)}</ul>`;

expect(d).toStrictEqual(
// tslint:disable-next-line: max-line-length
`<h1>Hello, World!</h1><ul><li>John Doe</li><li>Michael CEO</li><li>Vict Fisherman</li><li>Cash Black</li></ul>`
);
});

it(`renders external style`, () => {
// tslint:disable-next-line: max-line-length
const asyncExternalStyleTask = () => html/* css */`body { margin: 0; padding: 0; box-sizing: border-box; }`;
const d = html`<style>${asyncExternalStyleTask}</style>${helloWorld}`;

expect(d).toStrictEqual(
// tslint:disable-next-line: max-line-length
`<style>body { margin: 0; padding: 0; box-sizing: border-box; }</style><h1>Hello, World!</h1>`
);
});
Loading

0 comments on commit ccf2e0f

Please sign in to comment.