Skip to content

Commit 2be839e

Browse files
committed
fix: lost tree context
1 parent d109d83 commit 2be839e

1 file changed

Lines changed: 148 additions & 0 deletions

File tree

src/index.js

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
import fs from 'fs';
2+
import path from 'path';
3+
import util from 'util';
4+
import parseToPostHtml from 'posthtml-parser';
5+
import { match } from 'posthtml/lib/api';
6+
7+
const errors = {
8+
'EXTENDS_NO_SRC': '<extends> has no "src"',
9+
'BLOCK_NO_NAME': '<block> has no "name"',
10+
'UNEXPECTED_BLOCK': 'Unexpected block "%s"'
11+
};
12+
13+
export default (options = {}) => {
14+
return tree => {
15+
options.encoding = options.encoding || 'utf8';
16+
options.root = options.root || './';
17+
options.plugins = options.plugins || [];
18+
options.strict = Object.prototype.hasOwnProperty.call(options, 'strict') ? !!options.strict : true;
19+
options.slotTagName = options.slotTagName || 'block';
20+
options.fillTagName = options.fillTagName || 'block';
21+
options.tagName = options.tagName || 'extends';
22+
23+
tree = handleExtendsNodes(tree, options, tree.messages);
24+
25+
const blockNodes = getBlockNodes(tree, options.slotTagName);
26+
for (let blockName of Object.keys(blockNodes)) {
27+
let blockNode = blockNodes[blockName];
28+
blockNode.tag = false;
29+
blockNode.content = blockNode.content || [];
30+
blockNodes[blockName] = blockNode;
31+
}
32+
33+
return tree;
34+
};
35+
};
36+
37+
function handleExtendsNodes(tree, options, messages) {
38+
match.call(tree = applyPluginsToTree(tree, options.plugins), {tag: options.tagName}, extendsNode => {
39+
if (! extendsNode.attrs || ! extendsNode.attrs.src) {
40+
throw getError(errors.EXTENDS_NO_SRC);
41+
}
42+
43+
const layoutPath = path.resolve(options.root, extendsNode.attrs.src);
44+
const layoutHtml = fs.readFileSync(layoutPath, options.encoding);
45+
let layoutTree = handleExtendsNodes(applyPluginsToTree(parseToPostHtml(layoutHtml), options.plugins), options, messages);
46+
47+
extendsNode.tag = false;
48+
extendsNode.content = mergeExtendsAndLayout(layoutTree, extendsNode, options.strict, options.slotTagName, options.fillTagName);
49+
messages.push({
50+
type: 'dependency',
51+
file: layoutPath,
52+
from: options.from
53+
});
54+
55+
return extendsNode;
56+
});
57+
58+
return tree;
59+
}
60+
61+
function applyPluginsToTree(tree, plugins) {
62+
return plugins.reduce((tree, plugin) => tree = plugin(tree), tree);
63+
}
64+
65+
66+
function mergeExtendsAndLayout(layoutTree, extendsNode, strictNames, slotTagName, fillTagName) {
67+
const layoutBlockNodes = getBlockNodes(layoutTree, slotTagName);
68+
const extendsBlockNodes = getBlockNodes(extendsNode.content, fillTagName);
69+
70+
for (let layoutBlockName of Object.keys(layoutBlockNodes)) {
71+
let extendsBlockNode = extendsBlockNodes[layoutBlockName];
72+
if (!extendsBlockNode) {
73+
continue;
74+
}
75+
76+
let layoutBlockNode = layoutBlockNodes[layoutBlockName];
77+
layoutBlockNode.content = mergeContent(
78+
extendsBlockNode.content,
79+
layoutBlockNode.content,
80+
getBlockType(extendsBlockNode)
81+
);
82+
83+
delete extendsBlockNodes[layoutBlockName];
84+
}
85+
86+
if (strictNames) {
87+
for (let extendsBlockName of Object.keys(extendsBlockNodes)) {
88+
throw getError(errors.UNEXPECTED_BLOCK, extendsBlockName);
89+
}
90+
}
91+
92+
return layoutTree;
93+
}
94+
95+
96+
function mergeContent(extendBlockContent, layoutBlockContent, extendBlockType) {
97+
extendBlockContent = extendBlockContent || [];
98+
layoutBlockContent = layoutBlockContent || [];
99+
100+
switch (extendBlockType) {
101+
case 'replace':
102+
layoutBlockContent = extendBlockContent;
103+
break;
104+
105+
case 'prepend':
106+
layoutBlockContent = extendBlockContent.concat(layoutBlockContent);
107+
break;
108+
109+
case 'append':
110+
layoutBlockContent = layoutBlockContent.concat(extendBlockContent);
111+
break;
112+
}
113+
114+
return layoutBlockContent;
115+
}
116+
117+
118+
function getBlockType(blockNode) {
119+
let blockType = (blockNode.attrs && blockNode.attrs.type) || '';
120+
blockType = blockType.toLowerCase();
121+
if (['replace', 'prepend', 'append'].indexOf(blockType) === -1) {
122+
blockType = 'replace';
123+
}
124+
125+
return blockType;
126+
}
127+
128+
129+
function getBlockNodes(content = [], tag) {
130+
let blockNodes = {};
131+
132+
match.call(content, {tag}, node => {
133+
if (! node.attrs || ! node.attrs.name) {
134+
throw getError(errors.BLOCK_NO_NAME);
135+
}
136+
137+
blockNodes[node.attrs.name] = node;
138+
return node;
139+
});
140+
141+
return blockNodes;
142+
}
143+
144+
145+
function getError() {
146+
const message = util.format.apply(util, arguments);
147+
return new Error('[posthtml-extend] ' + message);
148+
}

0 commit comments

Comments
 (0)