Skip to content

Commit

Permalink
Merge pull request #151 from posthtml/next
Browse files Browse the repository at this point in the history
v2.1.0
  • Loading branch information
cossssmin authored Dec 16, 2024
2 parents 520edb1 + 898fc1a commit c783811
Show file tree
Hide file tree
Showing 21 changed files with 920 additions and 569 deletions.
26 changes: 23 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,26 @@
## 2.1.0-beta.2 (2024-11-29)

* chore: update `fileExtension` type 410d21e

## 2.1.0-beta.1 (2024-11-29)

* test: update tests 2c69ad8
* feat: support `fileExtension` as array 5175272
* refactor: use native for loops instead of lodash/each bcd80ef
* build(deps-dev): bump @vitest/coverage-v8 from 2.1.5 to 2.1.6 e07feae
* build(deps-dev): bump @biomejs/biome from 1.9.3 to 1.9.4 6e6323d
* build(deps): bump posthtml-parser from 0.12.0 to 0.12.1 e4f7e73
* build(deps): bump style-to-object from 1.0.7 to 1.0.8 ad81c84
* build(deps-dev): bump markdown-it-anchor from 9.1.0 to 9.2.0 0fbfcfa

## 2.0.0 (2024-07-25)

* **[BREAKING]** Node.js 18+ 907de89
* **[BREAKING]** renamed `blacklistAttributes` to `blocklistAttributes` 0cd302e
* migrate to Vitest 8cb2619
* fixed test for `posthtml-include` latest version 3d168d4
* feat: add types 7feb3af

## 2.0.0-beta.2 (2024-07-25)

* 2.0.0-beta.2 ([8078cce](https://github.com/posthtml/posthtml-components/commit/8078cce))
Expand Down Expand Up @@ -298,6 +321,3 @@
* Update readme ([42fa034](https://github.com/posthtml/posthtml-components/commit/42fa034))
* Update version ([0599426](https://github.com/posthtml/posthtml-components/commit/0599426))
* Update version ([27ab49c](https://github.com/posthtml/posthtml-components/commit/27ab49c))



123 changes: 74 additions & 49 deletions README.md

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions package-lock.json

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

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "posthtml-component",
"version": "2.0.0",
"version": "2.1.0-beta.2",
"description": "Laravel Blade-inspired components for PostHTML with slots, attributes as props, custom tags and more.",
"license": "MIT",
"repository": "posthtml/posthtml-components",
Expand Down
124 changes: 77 additions & 47 deletions src/find-path.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,20 @@ const folderSeparator = '.';
* @return {String|boolean} Full path or boolean false
*/
module.exports = (tag, options) => {
const fileNameFromTag = tag
.replace(options.tagPrefix, '')
.split(folderSeparator)
.join(path.sep)
.concat(folderSeparator, options.fileExtension);
const extensions = Array.isArray(options.fileExtension) ? options.fileExtension : [options.fileExtension];

const fileNamesFromTag = extensions.map(ext =>
tag
.replace(options.tagPrefix, '')
.split(folderSeparator)
.join(path.sep)
.concat(folderSeparator, ext)
)

try {
return tag.includes(options.namespaceSeparator) ?
searchInNamespaces(tag, fileNameFromTag.split(options.namespaceSeparator), options) :
searchInFolders(tag, fileNameFromTag, options);
return tag.includes(options.namespaceSeparator)
? searchInNamespaces(tag, fileNamesFromTag, options)
: searchInFolders(tag, fileNamesFromTag, options);
} catch (error) {
if (options.strict) {
throw new Error(error.message);
Expand All @@ -36,15 +40,17 @@ module.exports = (tag, options) => {
* Search component file in root
*
* @param {String} tag [tag name]
* @param {String} fileNameFromTag [filename converted from tag name]
* @param {Array} fileNamesFromTag [filename converted from tag name]
* @param {Object} options [posthtml options]
* @return {String|boolean} [custom tag root where the module is found]
*/
function searchInFolders(tag, fileNameFromTag, options) {
const componentPath = search(options.root, options.folders, fileNameFromTag, options.fileExtension);
function searchInFolders(tag, fileNamesFromTag, options) {
const componentPath = search(options.root, options.folders, fileNamesFromTag, options.fileExtension);

if (!componentPath) {
throw new Error(`[components] <${tag}> could not find ${fileNameFromTag} in the defined root paths (${options.folders.join(', ')})`);
throw new Error(
`[components] <${tag}> could not find ${fileNamesFromTag} in the defined root paths (${options.folders.join(', ')})`
);
}

return componentPath;
Expand All @@ -54,65 +60,89 @@ function searchInFolders(tag, fileNameFromTag, options) {
* Search component file within all defined namespaces
*
* @param {String} tag [tag name with namespace]
* @param {String} namespace [tag's namespace]
* @param {String} fileNameFromTag [filename converted from tag name]
* @param {Array} namespaceAndFileNames Array of [namespace]::[filename]
* @param {Object} options [posthtml options]
* @return {String|boolean} [custom tag root where the module is found]
*/
function searchInNamespaces(tag, [namespace, fileNameFromTag], options) {
const namespaceOption = options.namespaces.find(n => n.name === namespace.replace(options.tagPrefix, ''));
function searchInNamespaces(tag, namespaceAndFileNames, options) {
let result = '';

if (!namespaceOption) {
throw new Error(`[components] Unknown component namespace: ${namespace}.`);
}
for (const namespaceAndFileName of namespaceAndFileNames) {
const [namespace, fileNameFromTag] = namespaceAndFileName.split('::');

let componentPath;
const namespaceOption = options.namespaces.find(n => n.name === namespace.replace(options.tagPrefix, ''));

// 1) Check in custom root
if (namespaceOption.custom) {
componentPath = search(namespaceOption.custom, options.folders, fileNameFromTag, options.fileExtension);
}
if (!namespaceOption) {
throw new Error(`[components] Unknown component namespace: ${namespace}.`);
}

// 2) Check in base root
if (!componentPath) {
componentPath = search(namespaceOption.root, options.folders, fileNameFromTag, options.fileExtension);
}
let componentPath;

// 3) Check in fallback root
if (!componentPath && namespaceOption.fallback) {
componentPath = search(namespaceOption.fallback, options.folders, fileNameFromTag, options.fileExtension);
}
// 1) Check in custom root
if (namespaceOption.custom) {
componentPath = search(namespaceOption.custom, options.folders, fileNameFromTag, options.fileExtension);
}

// 2) Check in base root
if (!componentPath) {
componentPath = search(namespaceOption.root, options.folders, fileNameFromTag, options.fileExtension);
}

// 3) Check in fallback root
if (!componentPath && namespaceOption.fallback) {
componentPath = search(namespaceOption.fallback, options.folders, fileNameFromTag, options.fileExtension);
}

if (!componentPath && options.strict) {
throw new Error(`[components] <${tag}> could not find ${fileNameFromTag} in the defined namespace base path ${namespaceOption.root}`);
if (!componentPath) {
throw new Error(`[components] <${tag}> could not find ${fileNameFromTag} in the defined namespace paths.`);
}

result = componentPath;
}

return componentPath;
return result;
}

/**
* Main search component file function
* Main component file search function
*
* @param {String} root Base root or namespace root from options
* @param {Array} folders Folders from options
* @param {String} fileName Filename converted from tag name
* @param {String} extension File extension from options
* @param {String} root Base root or namespace root from `options`
* @param {Array} folders Folders to search in from `options`
* @param {Array} fileNames Filenames converted from tag name
* @param {Array} extensions File extension(s) from `options`
* @return {String|boolean} [custom tag root where the module is found]
*/
function search(root, folders, fileName, extension) {
function search(root, folders, fileNames, extensions) {
let componentPath;
let componentFound = false;

let componentFound = folders.some(folder => {
componentPath = path.join(path.resolve(root, folder), fileName);
return existsSync(componentPath);
});
fileNames = Array.isArray(fileNames) ? fileNames : [fileNames];

if (!componentFound) {
fileName = fileName.replace(`.${extension}`, `${path.sep}index.${extension}`);
for (const fileName of fileNames) {
componentFound = folders.some(folder => {
componentPath = path.join(path.resolve(root, folder), fileName);

return existsSync(componentPath);
});

if (componentFound) break;
}

if (!componentFound) {
for (const extension of extensions) {
for (const fileName of fileNames) {
const newFileName = fileName.replace(`.${extension}`, `${path.sep}index.${extension}`);

componentFound = folders.some(folder => {
componentPath = path.join(path.resolve(root, folder), newFileName);
return existsSync(componentPath);
});

if (componentFound) break;
}

if (componentFound) break;
}
}

return componentFound ? componentPath : false;
Expand Down
2 changes: 1 addition & 1 deletion src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export type PostHTMLComponents = {
*
* @default 'html'
*/
fileExtension?: string;
fileExtension?: string|string[];

/**
* Name of the tag that will be replaced with the content that is passed to the component.
Expand Down
42 changes: 28 additions & 14 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,17 +74,26 @@ module.exports = (options = {}) => tree => {
uniqueId,
isEnabled: prop => prop === true || prop === ''
};
// Additional element attributes, in case already exist in valid-attributes.js it will replace all attributes
// It should be an object with key as tag name and as value a function modifier which receive
// the default attributes and return an array of attributes. Example:
// { TAG: (attributes) => { attributes[] = 'attribute-name'; return attributes; } }

/**
* Additional element attributes. If they already exist in valid-attributes.js,
* it will replace all attributes. It should be an object with tag name as
* the key and a function modifier as the value, which will receive the
* default attributes and return an array of attributes.
*
* Example:
* { TAG: (attributes) => { attributes[] = 'attribute-name'; return attributes; } }
*/
options.elementAttributes = isPlainObject(options.elementAttributes) ? options.elementAttributes : {};
options.safelistAttributes = Array.isArray(options.safelistAttributes) ? options.safelistAttributes : [];
options.blocklistAttributes = Array.isArray(options.blocklistAttributes) ? options.blocklistAttributes : [];

// Merge customizer callback passed to lodash mergeWith
// for merge attribute `props` and all attributes starting with `merge:`
// @see https://lodash.com/docs/4.17.15#mergeWith
/**
* Merge customizer callback passed to `lodash.mergeWith` for merging
* attribute `props` and all attributes starting with `merge:`.
*
* @see {@link https://lodash.com/docs/4.17.15#mergeWith|Lodash}
*/
options.mergeCustomizer = options.mergeCustomizer || ((objectValue, sourceValue) => {
if (Array.isArray(objectValue)) {
return objectValue.concat(sourceValue);
Expand All @@ -105,6 +114,7 @@ module.exports = (options = {}) => tree => {

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

if (options.tagPrefix) {
options.matcher.push({tag: options.tagPrefix});
}
Expand All @@ -116,8 +126,10 @@ module.exports = (options = {}) => tree => {

options.folders = Array.isArray(options.folders) ? options.folders : [options.folders];
options.namespaces = Array.isArray(options.namespaces) ? options.namespaces : [options.namespaces];

options.namespaces.forEach((namespace, index) => {
options.namespaces[index].root = path.resolve(namespace.root);

if (namespace.fallback) {
options.namespaces[index].fallback = path.resolve(namespace.fallback);
}
Expand Down Expand Up @@ -148,14 +160,13 @@ module.exports = (options = {}) => tree => {
};
/* eslint-enable complexity */

// Used for reset aware props
// Used to reset aware props
let processCounter = 0;

/**
* @param {Object} options Plugin options
* @return {Object} PostHTML tree
*/

function processTree(options) {
const filledSlots = {};

Expand Down Expand Up @@ -227,15 +238,18 @@ function processTree(options) {

processAttributes(currentNode, attributes, props, options, aware);

// Remove attributes when value is 'null' or 'undefined'
// so we can conditionally add an attribute by setting value to 'undefined' or 'null'.
/**
* Remove attributes when value is 'null' or 'undefined' so we can
* conditionally add an attribute by setting the value to
* 'undefined' or 'null'.
*/
walk.call(currentNode, node => {
if (node && node.attrs) {
each(node.attrs, (value, key) => {
if (['undefined', 'null'].includes(value)) {
for (const key in node.attrs) {
if (node.attrs[key] === 'undefined' || node.attrs[key] === 'null') {
delete node.attrs[key];
}
});
}
}

return node;
Expand Down
Loading

0 comments on commit c783811

Please sign in to comment.