Skip to content

Commit 2e99880

Browse files
committed
Extend templates
1 parent f5a8558 commit 2e99880

File tree

7 files changed

+412
-0
lines changed

7 files changed

+412
-0
lines changed

.eslintrc

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"parser": "babel-eslint",
3+
"rules": {
4+
"indent": [2, 4, {"SwitchCase": 1}],
5+
"quotes": [2, "single"],
6+
"linebreak-style": [2, "unix"],
7+
"semi": [2, "always"],
8+
"camelcase": [2, {"properties": "always"}],
9+
"brace-style": [2, "1tbs", {"allowSingleLine": true}]
10+
},
11+
"env": {
12+
"es6": true,
13+
"node": true,
14+
"browser": false,
15+
"mocha": true
16+
},
17+
"extends": "eslint:recommended",
18+
"ecmaFeatures": {
19+
"experimentalObjectRestSpread": false
20+
}
21+
}

.npmignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
.gitignore
2+
.eslintrc
3+
node_modules/
4+
npm-debug.log
5+
/lib/*.es6
6+
/test/

README.md

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# PostHTML Extend
2+
[![npm version](https://badge.fury.io/js/posthtml-extend.svg)](http://badge.fury.io/js/posthtml-extend)
3+
4+
[PostHTML](https://github.com/posthtml/posthtml) plugin that allows a template to extend another templates (Jade-like).
5+
6+
7+
## Usage
8+
Let's say we have two parent templates:
9+
10+
`head.html`
11+
```html
12+
<head>
13+
<title><block name="title"></block></title>
14+
</head>
15+
```
16+
17+
`body.html`
18+
```html
19+
<body>
20+
<div class="content">
21+
<block name="content"></block>
22+
</div>
23+
24+
<footer>
25+
<block name="footer">footer</block>
26+
</fotter>
27+
</body>
28+
```
29+
30+
Now we can extend they:
31+
```js
32+
var posthtml = require('posthtml');
33+
var html = '<html>' +
34+
'<extends src="head.html">' +
35+
'<block name="title">How to use posthtml-extend</block>' +
36+
'</extends>' +
37+
'<extends src="body.html">' +
38+
'<block name="content">See README.md</block>' +
39+
'</extends>' +
40+
'</html>';
41+
42+
posthtml([require('posthtml-extend')({
43+
encoding: 'utf8', // Parent template encoding (default: 'utf8')
44+
root: './' // Path to parent template directory (default: './')
45+
})]).process(html).then(function (result) {
46+
console.log(result.html);
47+
});
48+
49+
// <html>
50+
// <head>
51+
// <title>How to use posthtml-extend</title>
52+
// </head>
53+
// <body>
54+
// <div class="content">See README.md</div>
55+
// <footer>footer</footer>
56+
// </body>
57+
// </html>
58+
```

index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module.exports = require('./lib/extend').default;

lib/extend.es6

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import fs from 'fs';
2+
import path from 'path';
3+
import util from 'util';
4+
import parseToPostHtml from 'posthtml-parser';
5+
6+
const errors = {
7+
'EXTENDS_NO_SRC': '<extends> has no "src"',
8+
'BLOCK_NO_NAME': '<block> has no "name"',
9+
'UNEXPECTED_BLOCK': 'Unexpected block "%s"'
10+
};
11+
12+
13+
export default (options = {}) => {
14+
return tree => {
15+
return extend(tree, options);
16+
};
17+
};
18+
19+
20+
function extend(tree, options) {
21+
options.encoding = options.encoding || 'utf8';
22+
options.root = options.root || './';
23+
24+
tree = handleExtendsNodes(tree, options);
25+
26+
const blockNodes = getBlockNodes(tree);
27+
for (let blockName of Object.keys(blockNodes)) {
28+
blockNodes[blockName].tag = false;
29+
}
30+
31+
return tree;
32+
}
33+
34+
35+
function handleExtendsNodes(tree, options) {
36+
getNodes(tree, 'extends').forEach(extendsNode => {
37+
if (! extendsNode.attrs || ! extendsNode.attrs.src) {
38+
throw getError(errors.EXTENDS_NO_SRC);
39+
}
40+
41+
const layoutPath = path.resolve(options.root, extendsNode.attrs.src);
42+
const layoutHtml = fs.readFileSync(layoutPath, options.encoding);
43+
let layoutTree = handleExtendsNodes(parseToPostHtml(layoutHtml), options);
44+
45+
extendsNode.tag = false;
46+
extendsNode.content = mergeExtendsAndLayout(layoutTree, extendsNode);
47+
});
48+
49+
return tree;
50+
}
51+
52+
53+
function mergeExtendsAndLayout(layoutTree, extendsNode) {
54+
const layoutBlockNodes = getBlockNodes(layoutTree);
55+
const extendsBlockNodes = getBlockNodes(extendsNode.content);
56+
57+
for (let layoutBlockName of Object.keys(layoutBlockNodes)) {
58+
let layoutBlockNode = layoutBlockNodes[layoutBlockName];
59+
let extendsBlockNode = extendsBlockNodes[layoutBlockName];
60+
if (extendsBlockNode) {
61+
layoutBlockNode.content = extendsBlockNode.content;
62+
delete extendsBlockNodes[layoutBlockName];
63+
}
64+
}
65+
66+
for (let extendsBlockName of Object.keys(extendsBlockNodes)) {
67+
throw getError(errors.UNEXPECTED_BLOCK, extendsBlockName);
68+
}
69+
70+
return layoutTree;
71+
}
72+
73+
74+
function getBlockNodes(content = []) {
75+
let blockNodes = {};
76+
77+
getNodes(content, 'block').forEach(node => {
78+
if (! node.attrs || ! node.attrs.name) {
79+
throw getError(errors.BLOCK_NO_NAME);
80+
}
81+
82+
blockNodes[node.attrs.name] = node;
83+
});
84+
85+
return blockNodes;
86+
}
87+
88+
89+
function getNodes(content = [], filterTag) {
90+
let nodes = [];
91+
content.forEach(node => {
92+
if (node.tag && (! filterTag || node.tag === filterTag)) {
93+
nodes.push(node);
94+
} else if (node.content) {
95+
nodes = nodes.concat(getNodes(node.content, filterTag));
96+
}
97+
});
98+
99+
return nodes;
100+
}
101+
102+
103+
function getError() {
104+
const message = util.format.apply(util, arguments);
105+
return new Error('[posthtml-extend] ' + message);
106+
}

package.json

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
{
2+
"name": "posthtml-extend",
3+
"version": "0.0.0",
4+
"description": "Templates extending (Jade-like)",
5+
"main": "index.js",
6+
"author": "Kirill Maltsev <maltsevkirill@gmail.com>",
7+
"license": "MIT",
8+
"scripts": {
9+
"compile": "rm -f lib/*.js && node_modules/.bin/babel -d lib/ lib/",
10+
"lint": "node_modules/.bin/eslint *.js lib/*.es6 test/",
11+
"pretest": "npm run lint && npm run compile",
12+
"test": "node_modules/.bin/_mocha --compilers js:babel-core/register",
13+
"prepublish": "npm run compile"
14+
},
15+
"keywords": [
16+
"posthtml",
17+
"posthtml-plugin",
18+
"html",
19+
"postproccessor",
20+
"jade",
21+
"extend",
22+
"template"
23+
],
24+
"babel": {
25+
"presets": [
26+
"es2015"
27+
]
28+
},
29+
"dependencies": {
30+
"posthtml-parser": "^0.1.1"
31+
},
32+
"devDependencies": {
33+
"babel-cli": "^6.3.17",
34+
"babel-core": "^6.3.26",
35+
"babel-eslint": "^4.1.6",
36+
"babel-preset-es2015": "^6.3.13",
37+
"bluebird": "^3.1.1",
38+
"eslint": "^1.10.3",
39+
"expect": "^1.13.4",
40+
"mocha": "^2.3.4",
41+
"posthtml": "^0.8.0",
42+
"proxyquire": "^1.7.3"
43+
},
44+
"repository": {
45+
"type": "git",
46+
"url": "git://github.com/maltsev/posthtml-extend.git"
47+
},
48+
"bugs": {
49+
"url": "https://github.com/maltsev/posthtml-extend/issues"
50+
},
51+
"homepage": "https://github.com/maltsev/posthtml-extend"
52+
}

0 commit comments

Comments
 (0)