Skip to content

Commit

Permalink
Added support for works with the same extends and modules syntax toge…
Browse files Browse the repository at this point in the history
…ther with x-tag for easy migration
  • Loading branch information
Damir committed Oct 13, 2022
1 parent 05582e6 commit d5f665e
Show file tree
Hide file tree
Showing 6 changed files with 106 additions and 11 deletions.
68 changes: 57 additions & 11 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,20 +30,24 @@ const slotTypes = {
function processNodes(tree, options, messages) {
tree = applyPluginsToTree(tree, options.plugins);

match.call(tree, [{tag: options.tagName}, {tag: options.tagRegExp}], node => {
match.call(tree, options.matcher, node => {
if (!node.attrs) {
node.attrs = {};
}

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

const filePath = node.attrs[attributePath] || findPathFromTagName(node, options);

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

delete node.attrs.src;
delete node.attrs[attributePath];

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

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

node.tag = false;
node.content = mergeSlots(layoutTree, node, options.strict, options.slotTagName);
node.content = mergeSlots(layoutTree, node, options.strict, options);

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

Expand Down Expand Up @@ -133,10 +137,11 @@ function parseLocals(options, {attrs}, html) {
* @param {Object} node
* @param {Boolean} strict
* @param {String} slotTagName
* @param {Boolean|String} fallbackSlotTagName
* @return {Object} tree
*/
function mergeSlots(tree, node, strict, slotTagName) {
const slots = getSlots(slotTagName, tree); // Slot in component.html
function mergeSlots(tree, node, strict, {slotTagName, fallbackSlotTagName}) {
const slots = getSlots(slotTagName, tree, fallbackSlotTagName); // Slot in component.html
const fillSlots = getSlots(slotTagName, node.content); // Slot in page.html

// Retrieve main content, means everything that is not inside slots
Expand Down Expand Up @@ -223,12 +228,16 @@ function mergeContent(slotContent, defaultContent, slotType) {
* Get all slots from content
* @param {String} tag
* @param {Object} content
* @param {Boolean|String} fallbackSlotTagName
* @return {Object}
*/
function getSlots(tag, content = []) {
function getSlots(tag, content = [], fallbackSlotTagName = false) {
const slots = {};

match.call(content, {tag}, node => {
// For compatibility with module slot name <content>
const matcher = fallbackSlotTagName === false ? {tag} : [{tag}, {tag: fallbackSlotTagName === true ? 'content' : fallbackSlotTagName}];

match.call(content, matcher, node => {
if (!node.attrs) {
node.attrs = {};
}
Expand Down Expand Up @@ -310,19 +319,25 @@ module.exports = (options = {}) => {
fileExtension: 'html',
tagPrefix: 'x-',
tagRegExp: new RegExp(`^${options.tagPrefix || 'x-'}`, 'i'),
defaultSlotRegex: new RegExp('^((?!(slot)).)*$'),
slotTagName: 'slot',
// Used for compatibility with modules plugin <content> slot, set to true only if you have migrated from modules plugin
fallbackSlotTagName: false,
tagName: 'component',
tagNames: [],
attribute: 'src',
attributes: [],
locals: {},
expressions: {},
plugins: [],
encoding: 'utf8',
strict: true,
scriptLocalAttribute: 'defaultLocals'
scriptLocalAttribute: 'defaultLocals',
matcher: []
},
...options
};

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

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

if (!Array.isArray(options.attributes)) {
options.attributes = [];
}

if (!Array.isArray(options.matcher)) {
options.matcher = options.matcher ? [options.matcher] : [];
}

if (!Array.isArray(options.tagNames)) {
options.tagNames = [];
}

/** Set one or multiple matcher */
if (options.matcher.length === 0) {
if (options.tagRegExp) {
options.matcher.push({tag: options.tagRegExp});
}

if (options.tagNames.length > 0) {
options.tagNames.forEach(tagName => {
options.matcher.push({tag: tagName});
});
} else if (options.tagName) {
options.matcher.push({tag: options.tagName});
}

if (options.matcher.length === 0) {
throw new Error('[components] No matcher found in options. Please to define almost one.');
}
}

return function (tree) {
tree = processNodes(tree, options, tree.messages);

const slots = getSlots(options.slotTagName, tree);
const slots = getSlots(options.slotTagName, tree, options.fallbackSlotTagName);

for (const slotName of Object.keys(slots)) {
const nodes = slots[slotName];
Expand Down
1 change: 1 addition & 0 deletions test/templates/components/module-with-extend.html
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<div><content></content></div>
1 change: 1 addition & 0 deletions test/templates/components/module.html
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<div><content></content></div>
7 changes: 7 additions & 0 deletions test/templates/layouts/extend-with-module.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<html>
<head><title>Extend With Module Layout</title></head>
<body>
<main><block name="content"></block></main>
<footer><block name="footer">footer content</block></footer>
</body>
</html>
7 changes: 7 additions & 0 deletions test/templates/layouts/extend.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<html>
<head><title>Extend Layout</title></head>
<body>
<main><block name="content"></block></main>
<footer><block name="footer">footer content</block></footer>
</body>
</html>
33 changes: 33 additions & 0 deletions test/test-plugins.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
'use strict';

const test = require('ava');
const plugin = require('../src');
const posthtml = require('posthtml');
const clean = html => html.replace(/(\n|\t)/g, '').trim();

test('Must work with posthtml-extend syntax', async t => {
const actual = `<extends src="layouts/extend.html"><block name="content">My Content</block></extends>`;
const expected = `<html><head><title>Extend Layout</title></head><body><main>My Content</main><footer>footer content</footer></body></html>`;

const html = await posthtml([plugin({root: './test/templates', tagName: 'extends', attribute: 'src', slotTagName: 'block'})]).process(actual).then(result => clean(result.html));

t.is(html, expected);
});

test('Must work with posthtml-modules syntax', async t => {
const actual = `<module href="components/module.html">My Module Content</module>`;
const expected = `<div>My Module Content</div>`;

const html = await posthtml([plugin({root: './test/templates', tagName: 'module', attribute: 'href', slotTagName: 'content'})]).process(actual).then(result => clean(result.html));

t.is(html, expected);
});

test('Must work with posthtml-extend and posthtml-modules syntax together', async t => {
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>`;
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>`;

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));

t.is(html, expected);
});

0 comments on commit d5f665e

Please sign in to comment.