Skip to content

Commit

Permalink
Fix parse attributes
Browse files Browse the repository at this point in the history
  • Loading branch information
thewebartisan7 committed Oct 17, 2022
1 parent 44a6154 commit 8321415
Show file tree
Hide file tree
Showing 5 changed files with 103 additions and 38 deletions.
29 changes: 28 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
"posthtml-attrs-parser": "^0.1.1",
"posthtml-expressions": "^1.9.0",
"posthtml-match-helper": "^1.0.3",
"posthtml-parser": "^0.11.0"
"posthtml-parser": "^0.11.0",
"style-to-object": "^0.3.0"
},
"devDependencies": {
"@commitlint/cli": "^12.0.1",
Expand Down
101 changes: 69 additions & 32 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const fs = require('fs');
const path = require('path');
const {inspect} = require('util');
const {sha256} = require('js-sha256');
const styleToObject = require('style-to-object');
const expressions = require('posthtml-expressions');
const scriptDataLocals = require('posthtml-expressions/lib/locals');
const {parser: parseToPostHtml} = require('posthtml-parser');
Expand All @@ -12,6 +13,7 @@ const parseAttrs = require('posthtml-attrs-parser');
const {match} = require('posthtml/lib/api');
const merge = require('deepmerge');
const findPathFromTagName = require('./find-path');
// const posthtml = require('posthtml');

const debug = true;

Expand Down Expand Up @@ -63,7 +65,7 @@ function processNodes(tree, options, messages) {
options.expressions.locals = {...options.locals, ...options.aware};

const defaultSlotName = sha256(filePath);
const slotsLocals = parseSlotsLocals(options.slotTagName, html, node.content, defaultSlotName);
const slotsLocals = parseSlotsLocals(options.fillTagName, html, node.content, defaultSlotName);
const {attributes, locals} = parseLocals(options, slotsLocals, node, html);

options.expressions.locals = attributes;
Expand All @@ -74,33 +76,9 @@ function processNodes(tree, options, messages) {
const layoutTree = processNodes(applyPluginsToTree(html, plugins), options, messages);

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

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

if (index !== -1) {
// Map component attributes that it's not defined
// as locals to first element of node
// for now only class and style
const nodeAttrs = parseAttrs(node.content[index].attrs);

Object.keys(attributes).forEach(attr => {
if (typeof locals[attr] === 'undefined') {
if (['class'].includes(attr)) {
if (typeof nodeAttrs[attr] === 'undefined') {
nodeAttrs[attr] = [];
}

nodeAttrs[attr].push(attributes[attr]);
} else if (['style'].includes(attr)) {
// Append style ?
nodeAttrs[attr] = attributes[attr];
}
}
});
node.content = mergeSlots(layoutTree, node, options, defaultSlotName);

node.content[index].attrs = nodeAttrs.compose();
}
parseAttributes(node, attributes, locals, options);

messages.push({
type: 'dependency',
Expand Down Expand Up @@ -266,26 +244,77 @@ function parseSlotsLocals(tag, html, content, defaultSlotName) {
return slots;
}

/**
* Map component attributes that it's not defined as locals to first element of node
* @param {Object} node
* @param {Object} attributes
* @param {Object} locals
* @param {Object} options
* @return {void}
*/
function parseAttributes(node, attributes, locals, options) {
const index = node.content.findIndex(content => typeof content === 'object');

if (index !== -1) {
const nodeAttrs = parseAttrs(node.content[index].attrs, options.attrsParserRules);

Object.keys(attributes).forEach(attr => {
if (typeof locals[attr] === 'undefined') {
if (['class'].includes(attr)) {
// Merge class
if (typeof nodeAttrs.class === 'undefined') {
nodeAttrs.class = [];
}

nodeAttrs.class.push(attributes.class);

delete attributes.class;
} else if (['override:class'].includes(attr)) {
// Override class
nodeAttrs.class = attributes['override:class'];

delete attributes['override:class'];
} else if (['style'].includes(attr)) {
// Merge style
if (typeof nodeAttrs.style === 'undefined') {
nodeAttrs.style = {};
}

nodeAttrs.style = Object.assign(nodeAttrs.style, styleToObject(attributes.style));
delete attributes.style;
} else if (['override:style'].includes(attr)) {
// Override style
nodeAttrs.style = attributes['override:style'];
delete attributes['override:style'];
}
}
});

node.content[index].attrs = nodeAttrs.compose();
}
}

/**
* Merge slots content
* @param {Object} tree
* @param {Object} node
* @param {Boolean} strict
* @param {String} slotTagName
* @param {String} fillTagName
* @param {Boolean|String} fallbackSlotTagName
* @param defaultSlotName
* @return {Object} tree
*/
function mergeSlots(tree, node, strict, {slotTagName, fallbackSlotTagName}, defaultSlotName) {
function mergeSlots(tree, node, {strict, slotTagName, fillTagName, fallbackSlotTagName}, defaultSlotName) {
const slots = getSlots(slotTagName, tree, defaultSlotName, fallbackSlotTagName); // Slot in component.html
const fillSlots = getSlots(slotTagName, node.content, defaultSlotName); // Slot in page.html
const fillSlots = getSlots(fillTagName, node.content, defaultSlotName); // Slot in page.html
const clean = content => content.replace(/(\n|\t)/g, '').trim();

// Retrieve main content, means everything that is not inside slots
if (node.content) {
const contentOutsideSlots = node.content.filter(content => (content.tag !== slotTagName));
const contentOutsideSlots = node.content.filter(content => (content.tag !== fillTagName));
if (contentOutsideSlots.filter(c => typeof c !== 'string' || clean(c) !== '').length > 0) {
fillSlots[defaultSlotName] = [{tag: slotTagName, attrs: {name: defaultSlotName}, content: [...contentOutsideSlots]}];
fillSlots[defaultSlotName] = [{tag: fillTagName, attrs: {name: defaultSlotName}, content: [...contentOutsideSlots]}];
}

// Replace <content> with <block>
Expand Down Expand Up @@ -429,6 +458,12 @@ function applyPluginsToTree(tree, plugins) {
}, tree);
}

// function processWithPostHtml(html, options = {}, plugins = []) {
// return posthtml(plugins)
// .process(html, options)
// .then(result => result.tree);
// }

module.exports = (options = {}) => {
options = {
...{
Expand All @@ -441,6 +476,7 @@ module.exports = (options = {}) => {
tagPrefix: 'x-',
tagRegExp: new RegExp(`^${options.tagPrefix || 'x-'}`, 'i'),
slotTagName: 'slot',
fillTagName: 'slot',
// Used for compatibility with modules plugin <content> slot, set to true only if you have migrated from modules plugin
fallbackSlotTagName: false,
tagName: 'component',
Expand All @@ -452,7 +488,8 @@ module.exports = (options = {}) => {
encoding: 'utf8',
scriptLocalAttribute: 'props',
matcher: [],
strict: true
strict: true,
attrsParserRules: {}
},
...options
};
Expand Down
4 changes: 2 additions & 2 deletions test/test-locals.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ test('Must process attributes as locals', async t => {
});

test('Must process default attributes and map style and class for the first node', async t => {
const actual = `<component src="components/component-mapped-attributes.html" title="My Title" class="bg-light p-2" style="display: flex; font-size: 20px;"></component>`;
const expected = `<div class="text-dark m-3 bg-light p-2" style="display: flex; font-size: 20px;">My Title Default body</div>`;
const actual = `<component src="components/component-mapped-attributes.html" title="My Title" class="bg-light p-2" style="display: flex; font-size: 20px"></component>`;
const expected = `<div class="text-dark m-3 bg-light p-2" style="display: flex; font-size: 20px">My Title Default body</div>`;

const html = await posthtml([plugin({root: './test/templates'})]).process(actual).then(result => clean(result.html));

Expand Down
4 changes: 2 additions & 2 deletions test/test-plugins.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ 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));
const html = await posthtml([plugin({root: './test/templates', tagName: 'extends', attribute: 'src', slotTagName: 'block', fillTagName: 'block'})]).process(actual).then(result => clean(result.html));

t.is(html, expected);
});
Expand All @@ -27,7 +27,7 @@ test('Must work with posthtml-extend and posthtml-modules syntax together', asyn
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));
const html = await posthtml([plugin({root: './test/templates', tagNames: ['extends', 'module'], attributes: ['src', 'href'], slotTagName: 'block', fillTagName: 'block', fallbackSlotTagName: true})]).process(actual).then(result => clean(result.html));

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

0 comments on commit 8321415

Please sign in to comment.