Skip to content

Commit d5f665e

Browse files
author
Damir
committed
Added support for works with the same extends and modules syntax together with x-tag for easy migration
1 parent 05582e6 commit d5f665e

File tree

6 files changed

+106
-11
lines changed

6 files changed

+106
-11
lines changed

src/index.js

+57-11
Original file line numberDiff line numberDiff line change
@@ -30,20 +30,24 @@ const slotTypes = {
3030
function processNodes(tree, options, messages) {
3131
tree = applyPluginsToTree(tree, options.plugins);
3232

33-
match.call(tree, [{tag: options.tagName}, {tag: options.tagRegExp}], node => {
33+
match.call(tree, options.matcher, node => {
3434
if (!node.attrs) {
3535
node.attrs = {};
3636
}
3737

38-
const filePath = node.attrs.src || findPathFromTagName(node, options);
38+
// For compatibility with extends and modules plugins
39+
// we want to support multiple attributes for define path
40+
const attributePath = (options.attributes.length > 0 && options.attributes.find(attribute => node.attrs[attribute])) || options.attribute;
41+
42+
const filePath = node.attrs[attributePath] || findPathFromTagName(node, options);
3943

4044
// Return node as-is when strict mode is disabled
4145
// otherwise raise error happen in find-path.js
4246
if (!filePath) {
4347
return node;
4448
}
4549

46-
delete node.attrs.src;
50+
delete node.attrs[attributePath];
4751

4852
const layoutPath = path.resolve(options.root, filePath);
4953

@@ -60,7 +64,7 @@ function processNodes(tree, options, messages) {
6064
const layoutTree = processNodes(applyPluginsToTree(html, plugins), options, messages);
6165

6266
node.tag = false;
63-
node.content = mergeSlots(layoutTree, node, options.strict, options.slotTagName);
67+
node.content = mergeSlots(layoutTree, node, options.strict, options);
6468

6569
const index = node.content.findIndex(content => typeof content === 'object');
6670

@@ -133,10 +137,11 @@ function parseLocals(options, {attrs}, html) {
133137
* @param {Object} node
134138
* @param {Boolean} strict
135139
* @param {String} slotTagName
140+
* @param {Boolean|String} fallbackSlotTagName
136141
* @return {Object} tree
137142
*/
138-
function mergeSlots(tree, node, strict, slotTagName) {
139-
const slots = getSlots(slotTagName, tree); // Slot in component.html
143+
function mergeSlots(tree, node, strict, {slotTagName, fallbackSlotTagName}) {
144+
const slots = getSlots(slotTagName, tree, fallbackSlotTagName); // Slot in component.html
140145
const fillSlots = getSlots(slotTagName, node.content); // Slot in page.html
141146

142147
// Retrieve main content, means everything that is not inside slots
@@ -223,12 +228,16 @@ function mergeContent(slotContent, defaultContent, slotType) {
223228
* Get all slots from content
224229
* @param {String} tag
225230
* @param {Object} content
231+
* @param {Boolean|String} fallbackSlotTagName
226232
* @return {Object}
227233
*/
228-
function getSlots(tag, content = []) {
234+
function getSlots(tag, content = [], fallbackSlotTagName = false) {
229235
const slots = {};
230236

231-
match.call(content, {tag}, node => {
237+
// For compatibility with module slot name <content>
238+
const matcher = fallbackSlotTagName === false ? {tag} : [{tag}, {tag: fallbackSlotTagName === true ? 'content' : fallbackSlotTagName}];
239+
240+
match.call(content, matcher, node => {
232241
if (!node.attrs) {
233242
node.attrs = {};
234243
}
@@ -310,19 +319,25 @@ module.exports = (options = {}) => {
310319
fileExtension: 'html',
311320
tagPrefix: 'x-',
312321
tagRegExp: new RegExp(`^${options.tagPrefix || 'x-'}`, 'i'),
313-
defaultSlotRegex: new RegExp('^((?!(slot)).)*$'),
314322
slotTagName: 'slot',
323+
// Used for compatibility with modules plugin <content> slot, set to true only if you have migrated from modules plugin
324+
fallbackSlotTagName: false,
315325
tagName: 'component',
326+
tagNames: [],
327+
attribute: 'src',
328+
attributes: [],
316329
locals: {},
317330
expressions: {},
318331
plugins: [],
319332
encoding: 'utf8',
320333
strict: true,
321-
scriptLocalAttribute: 'defaultLocals'
334+
scriptLocalAttribute: 'defaultLocals',
335+
matcher: []
322336
},
323337
...options
324338
};
325339

340+
/** Set root, roots and namespace's roots */
326341
options.root = path.resolve(options.root);
327342

328343
options.roots = Array.isArray(options.roots) ? options.roots : [options.roots];
@@ -344,10 +359,41 @@ module.exports = (options = {}) => {
344359
}
345360
});
346361

362+
if (!Array.isArray(options.attributes)) {
363+
options.attributes = [];
364+
}
365+
366+
if (!Array.isArray(options.matcher)) {
367+
options.matcher = options.matcher ? [options.matcher] : [];
368+
}
369+
370+
if (!Array.isArray(options.tagNames)) {
371+
options.tagNames = [];
372+
}
373+
374+
/** Set one or multiple matcher */
375+
if (options.matcher.length === 0) {
376+
if (options.tagRegExp) {
377+
options.matcher.push({tag: options.tagRegExp});
378+
}
379+
380+
if (options.tagNames.length > 0) {
381+
options.tagNames.forEach(tagName => {
382+
options.matcher.push({tag: tagName});
383+
});
384+
} else if (options.tagName) {
385+
options.matcher.push({tag: options.tagName});
386+
}
387+
388+
if (options.matcher.length === 0) {
389+
throw new Error('[components] No matcher found in options. Please to define almost one.');
390+
}
391+
}
392+
347393
return function (tree) {
348394
tree = processNodes(tree, options, tree.messages);
349395

350-
const slots = getSlots(options.slotTagName, tree);
396+
const slots = getSlots(options.slotTagName, tree, options.fallbackSlotTagName);
351397

352398
for (const slotName of Object.keys(slots)) {
353399
const nodes = slots[slotName];
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<div><content></content></div>

test/templates/components/module.html

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<div><content></content></div>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<html>
2+
<head><title>Extend With Module Layout</title></head>
3+
<body>
4+
<main><block name="content"></block></main>
5+
<footer><block name="footer">footer content</block></footer>
6+
</body>
7+
</html>

test/templates/layouts/extend.html

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<html>
2+
<head><title>Extend Layout</title></head>
3+
<body>
4+
<main><block name="content"></block></main>
5+
<footer><block name="footer">footer content</block></footer>
6+
</body>
7+
</html>

test/test-plugins.js

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
'use strict';
2+
3+
const test = require('ava');
4+
const plugin = require('../src');
5+
const posthtml = require('posthtml');
6+
const clean = html => html.replace(/(\n|\t)/g, '').trim();
7+
8+
test('Must work with posthtml-extend syntax', async t => {
9+
const actual = `<extends src="layouts/extend.html"><block name="content">My Content</block></extends>`;
10+
const expected = `<html><head><title>Extend Layout</title></head><body><main>My Content</main><footer>footer content</footer></body></html>`;
11+
12+
const html = await posthtml([plugin({root: './test/templates', tagName: 'extends', attribute: 'src', slotTagName: 'block'})]).process(actual).then(result => clean(result.html));
13+
14+
t.is(html, expected);
15+
});
16+
17+
test('Must work with posthtml-modules syntax', async t => {
18+
const actual = `<module href="components/module.html">My Module Content</module>`;
19+
const expected = `<div>My Module Content</div>`;
20+
21+
const html = await posthtml([plugin({root: './test/templates', tagName: 'module', attribute: 'href', slotTagName: 'content'})]).process(actual).then(result => clean(result.html));
22+
23+
t.is(html, expected);
24+
});
25+
26+
test('Must work with posthtml-extend and posthtml-modules syntax together', async t => {
27+
const actual = `<extends src="layouts/extend-with-module.html"><block name="content"><module href="components/module-with-extend.html">My Module Content</module></block></extends>`;
28+
const expected = `<html><head><title>Extend With Module Layout</title></head><body><main><div>My Module Content</div></main><footer>footer content</footer></body></html>`;
29+
30+
const html = await posthtml([plugin({root: './test/templates', tagNames: ['extends', 'module'], attributes: ['src', 'href'], slotTagName: 'block', fallbackSlotTagName: true})]).process(actual).then(result => clean(result.html));
31+
32+
t.is(html, expected);
33+
});

0 commit comments

Comments
 (0)