Skip to content

Commit

Permalink
Added Handlebars for Deno
Browse files Browse the repository at this point in the history
  • Loading branch information
irustm committed May 3, 2020
0 parents commit 1ad88bc
Show file tree
Hide file tree
Showing 9 changed files with 185 additions and 0 deletions.
19 changes: 19 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
name: test

on: [push, pull_request]

jobs:
build:
runs-on: ubuntu-16.04
strategy:
matrix:
deno: [0.42.0]
name: Deno ${{ matrix.deno }}
steps:
- uses: actions/checkout@master
- name: Setup deno
uses: denolib/setup-deno@master
with:
deno-version: ${{ matrix.deno }}
- name: Test
run: deno test -A
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
## Handlebars template render for Deno

Official handlebars docs: [Guide](https://handlebarsjs.com/guide)

### How to use renderer

```ts
// First, create instance of Handlebars

const handle = new Handlebars();

// or with custom config
const handle = new Handlebars({
...
});

// by default uses this config:
const DEFAULT_HANDLEBARS_CONFIG: HandlebarsConfig = {
baseDir: 'views',
extname: '.hbs',
layoutsDir: 'layouts/',
partialsDir: 'partials/',
defaultLayout: 'main',
helpers: undefined,
compilerOptions: undefined,
};

// Then render page
await handle.renderView('index', { name: 'Alosaur' })

```
106 changes: 106 additions & 0 deletions mod.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import HandlebarsJS from 'https://dev.jspm.io/[email protected]';
import { walk } from 'https://deno.land/std/fs/mod.ts';
import { globToRegExp, normalize, join } from 'https://deno.land/std/path/mod.ts';
const { readFile } = Deno;

interface HandlebarsConfig {
baseDir: string;
extname: string;
layoutsDir: string;
partialsDir: string;
defaultLayout: string;
helpers: any;
compilerOptions: any;
}

const DEFAULT_HANDLEBARS_CONFIG: HandlebarsConfig = {
baseDir: 'views',
extname: '.hbs',
layoutsDir: 'layouts/',
partialsDir: 'partials/',
defaultLayout: 'main',
helpers: undefined,
compilerOptions: undefined,
};

function getNormalizePath(path: string) {
return normalize(path).replace(/\\/g, '/');
}

export class Handlebars {
constructor(private config: HandlebarsConfig = DEFAULT_HANDLEBARS_CONFIG) {
this.config = { ...DEFAULT_HANDLEBARS_CONFIG, ...config };
}

/**
* Processes of rendering view
*
* @param view
* @param context
* @param layout
*/
public async renderView(view: string, context?: Object, layout?: string): Promise<string> {
if (!view) {
console.warn('View is null');
return '';
}

const config: HandlebarsConfig = this.config as HandlebarsConfig;

const partialsPathes = await this.getTemplatesPath(join(config.baseDir, config.partialsDir));
partialsPathes && (await this.registerPartials(partialsPathes));

const path = join(config.baseDir, view + config.extname);
const body: string = await this.render(path, context);

layout = (layout as string) || config.defaultLayout;

if (layout) {
const layoutPath: string = join(config.baseDir, config.layoutsDir, layout + config.extname);

return this.render(layoutPath, { ...context, body });
}

return body;
}

/**
* Processes on render without partials and layouts
*/
public async render(path: string, context?: Object): Promise<string> {
// TODO: use cashe
const source: string = new TextDecoder().decode(await readFile(path));
const template = HandlebarsJS.compile(source, this.config!.compilerOptions);

return template(context);
}

/**
* Processes on register partials
*/
private async registerPartials(pathes: string[]) {
for (const path of pathes) {
const templateName: string = path
.replace(this.config.baseDir + '/' + this.config!.partialsDir, '')
.replace(new RegExp(this.config!.extname + '$'), '');
const source: string = new TextDecoder().decode(await readFile(path));

HandlebarsJS.registerPartial(templateName, source);
}
}

/**
* Gets template pathes with glob match
*/
private async getTemplatesPath(path: string): Promise<string[]> {
const arr: string[] = [];

for await (const w of walk(path, { match: [globToRegExp('**/*' + this.config!.extname)] })) {
if (w.isFile) {
arr.push(getNormalizePath(w.path));
}
}

return arr;
}
}
17 changes: 17 additions & 0 deletions tests/basic.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Handlebars } from '../mod.ts';
import { assert } from 'https://deno.land/[email protected]/testing/asserts.ts';
const { test } = Deno;

test({
name: 'Basic render test',
async fn() {
const handle = new Handlebars();
const text = await handle.renderView('index', { name: 'Alosaur' });
assert(text.includes('header!'));
assert(text.includes('This is index page My name is Alosaur'));
assert(text.includes('title!'));
assert(text.includes('footer!'));
},
});

// TODO Add test with full contains
3 changes: 3 additions & 0 deletions views/index.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
This is index page My name is {{name}}

{{> page/title }}
5 changes: 5 additions & 0 deletions views/layouts/main.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{{> head }}

{{ body }}

{{> foot }}
2 changes: 2 additions & 0 deletions views/partials/foot.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
----
footer!
1 change: 1 addition & 0 deletions views/partials/head.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
header!
1 change: 1 addition & 0 deletions views/partials/page/title.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
title!

0 comments on commit 1ad88bc

Please sign in to comment.