Skip to content

Commit c783811

Browse files
authored
Merge pull request #151 from posthtml/next
v2.1.0
2 parents 520edb1 + 898fc1a commit c783811

21 files changed

+920
-569
lines changed

CHANGELOG.md

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,26 @@
1+
## 2.1.0-beta.2 (2024-11-29)
2+
3+
* chore: update `fileExtension` type 410d21e
4+
5+
## 2.1.0-beta.1 (2024-11-29)
6+
7+
* test: update tests 2c69ad8
8+
* feat: support `fileExtension` as array 5175272
9+
* refactor: use native for loops instead of lodash/each bcd80ef
10+
* build(deps-dev): bump @vitest/coverage-v8 from 2.1.5 to 2.1.6 e07feae
11+
* build(deps-dev): bump @biomejs/biome from 1.9.3 to 1.9.4 6e6323d
12+
* build(deps): bump posthtml-parser from 0.12.0 to 0.12.1 e4f7e73
13+
* build(deps): bump style-to-object from 1.0.7 to 1.0.8 ad81c84
14+
* build(deps-dev): bump markdown-it-anchor from 9.1.0 to 9.2.0 0fbfcfa
15+
16+
## 2.0.0 (2024-07-25)
17+
18+
* **[BREAKING]** Node.js 18+ 907de89
19+
* **[BREAKING]** renamed `blacklistAttributes` to `blocklistAttributes` 0cd302e
20+
* migrate to Vitest 8cb2619
21+
* fixed test for `posthtml-include` latest version 3d168d4
22+
* feat: add types 7feb3af
23+
124
## 2.0.0-beta.2 (2024-07-25)
225

326
* 2.0.0-beta.2 ([8078cce](https://github.com/posthtml/posthtml-components/commit/8078cce))
@@ -298,6 +321,3 @@
298321
* Update readme ([42fa034](https://github.com/posthtml/posthtml-components/commit/42fa034))
299322
* Update version ([0599426](https://github.com/posthtml/posthtml-components/commit/0599426))
300323
* Update version ([27ab49c](https://github.com/posthtml/posthtml-components/commit/27ab49c))
301-
302-
303-

README.md

Lines changed: 74 additions & 49 deletions
Large diffs are not rendered by default.

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "posthtml-component",
3-
"version": "2.0.0",
3+
"version": "2.1.0-beta.2",
44
"description": "Laravel Blade-inspired components for PostHTML with slots, attributes as props, custom tags and more.",
55
"license": "MIT",
66
"repository": "posthtml/posthtml-components",

src/find-path.js

Lines changed: 77 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,20 @@ const folderSeparator = '.';
1313
* @return {String|boolean} Full path or boolean false
1414
*/
1515
module.exports = (tag, options) => {
16-
const fileNameFromTag = tag
17-
.replace(options.tagPrefix, '')
18-
.split(folderSeparator)
19-
.join(path.sep)
20-
.concat(folderSeparator, options.fileExtension);
16+
const extensions = Array.isArray(options.fileExtension) ? options.fileExtension : [options.fileExtension];
17+
18+
const fileNamesFromTag = extensions.map(ext =>
19+
tag
20+
.replace(options.tagPrefix, '')
21+
.split(folderSeparator)
22+
.join(path.sep)
23+
.concat(folderSeparator, ext)
24+
)
2125

2226
try {
23-
return tag.includes(options.namespaceSeparator) ?
24-
searchInNamespaces(tag, fileNameFromTag.split(options.namespaceSeparator), options) :
25-
searchInFolders(tag, fileNameFromTag, options);
27+
return tag.includes(options.namespaceSeparator)
28+
? searchInNamespaces(tag, fileNamesFromTag, options)
29+
: searchInFolders(tag, fileNamesFromTag, options);
2630
} catch (error) {
2731
if (options.strict) {
2832
throw new Error(error.message);
@@ -36,15 +40,17 @@ module.exports = (tag, options) => {
3640
* Search component file in root
3741
*
3842
* @param {String} tag [tag name]
39-
* @param {String} fileNameFromTag [filename converted from tag name]
43+
* @param {Array} fileNamesFromTag [filename converted from tag name]
4044
* @param {Object} options [posthtml options]
4145
* @return {String|boolean} [custom tag root where the module is found]
4246
*/
43-
function searchInFolders(tag, fileNameFromTag, options) {
44-
const componentPath = search(options.root, options.folders, fileNameFromTag, options.fileExtension);
47+
function searchInFolders(tag, fileNamesFromTag, options) {
48+
const componentPath = search(options.root, options.folders, fileNamesFromTag, options.fileExtension);
4549

4650
if (!componentPath) {
47-
throw new Error(`[components] <${tag}> could not find ${fileNameFromTag} in the defined root paths (${options.folders.join(', ')})`);
51+
throw new Error(
52+
`[components] <${tag}> could not find ${fileNamesFromTag} in the defined root paths (${options.folders.join(', ')})`
53+
);
4854
}
4955

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

65-
if (!namespaceOption) {
66-
throw new Error(`[components] Unknown component namespace: ${namespace}.`);
67-
}
70+
for (const namespaceAndFileName of namespaceAndFileNames) {
71+
const [namespace, fileNameFromTag] = namespaceAndFileName.split('::');
6872

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

71-
// 1) Check in custom root
72-
if (namespaceOption.custom) {
73-
componentPath = search(namespaceOption.custom, options.folders, fileNameFromTag, options.fileExtension);
74-
}
75+
if (!namespaceOption) {
76+
throw new Error(`[components] Unknown component namespace: ${namespace}.`);
77+
}
7578

76-
// 2) Check in base root
77-
if (!componentPath) {
78-
componentPath = search(namespaceOption.root, options.folders, fileNameFromTag, options.fileExtension);
79-
}
79+
let componentPath;
8080

81-
// 3) Check in fallback root
82-
if (!componentPath && namespaceOption.fallback) {
83-
componentPath = search(namespaceOption.fallback, options.folders, fileNameFromTag, options.fileExtension);
84-
}
81+
// 1) Check in custom root
82+
if (namespaceOption.custom) {
83+
componentPath = search(namespaceOption.custom, options.folders, fileNameFromTag, options.fileExtension);
84+
}
85+
86+
// 2) Check in base root
87+
if (!componentPath) {
88+
componentPath = search(namespaceOption.root, options.folders, fileNameFromTag, options.fileExtension);
89+
}
90+
91+
// 3) Check in fallback root
92+
if (!componentPath && namespaceOption.fallback) {
93+
componentPath = search(namespaceOption.fallback, options.folders, fileNameFromTag, options.fileExtension);
94+
}
8595

86-
if (!componentPath && options.strict) {
87-
throw new Error(`[components] <${tag}> could not find ${fileNameFromTag} in the defined namespace base path ${namespaceOption.root}`);
96+
if (!componentPath) {
97+
throw new Error(`[components] <${tag}> could not find ${fileNameFromTag} in the defined namespace paths.`);
98+
}
99+
100+
result = componentPath;
88101
}
89102

90-
return componentPath;
103+
return result;
91104
}
92105

93106
/**
94-
* Main search component file function
107+
* Main component file search function
95108
*
96-
* @param {String} root Base root or namespace root from options
97-
* @param {Array} folders Folders from options
98-
* @param {String} fileName Filename converted from tag name
99-
* @param {String} extension File extension from options
109+
* @param {String} root Base root or namespace root from `options`
110+
* @param {Array} folders Folders to search in from `options`
111+
* @param {Array} fileNames Filenames converted from tag name
112+
* @param {Array} extensions File extension(s) from `options`
100113
* @return {String|boolean} [custom tag root where the module is found]
101114
*/
102-
function search(root, folders, fileName, extension) {
115+
function search(root, folders, fileNames, extensions) {
103116
let componentPath;
117+
let componentFound = false;
104118

105-
let componentFound = folders.some(folder => {
106-
componentPath = path.join(path.resolve(root, folder), fileName);
107-
return existsSync(componentPath);
108-
});
119+
fileNames = Array.isArray(fileNames) ? fileNames : [fileNames];
109120

110-
if (!componentFound) {
111-
fileName = fileName.replace(`.${extension}`, `${path.sep}index.${extension}`);
121+
for (const fileName of fileNames) {
112122
componentFound = folders.some(folder => {
113123
componentPath = path.join(path.resolve(root, folder), fileName);
124+
114125
return existsSync(componentPath);
115126
});
127+
128+
if (componentFound) break;
129+
}
130+
131+
if (!componentFound) {
132+
for (const extension of extensions) {
133+
for (const fileName of fileNames) {
134+
const newFileName = fileName.replace(`.${extension}`, `${path.sep}index.${extension}`);
135+
136+
componentFound = folders.some(folder => {
137+
componentPath = path.join(path.resolve(root, folder), newFileName);
138+
return existsSync(componentPath);
139+
});
140+
141+
if (componentFound) break;
142+
}
143+
144+
if (componentFound) break;
145+
}
116146
}
117147

118148
return componentFound ? componentPath : false;

src/index.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ export type PostHTMLComponents = {
6060
*
6161
* @default 'html'
6262
*/
63-
fileExtension?: string;
63+
fileExtension?: string|string[];
6464

6565
/**
6666
* Name of the tag that will be replaced with the content that is passed to the component.

src/index.js

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -74,17 +74,26 @@ module.exports = (options = {}) => tree => {
7474
uniqueId,
7575
isEnabled: prop => prop === true || prop === ''
7676
};
77-
// Additional element attributes, in case already exist in valid-attributes.js it will replace all attributes
78-
// It should be an object with key as tag name and as value a function modifier which receive
79-
// the default attributes and return an array of attributes. Example:
80-
// { TAG: (attributes) => { attributes[] = 'attribute-name'; return attributes; } }
77+
78+
/**
79+
* Additional element attributes. If they already exist in valid-attributes.js,
80+
* it will replace all attributes. It should be an object with tag name as
81+
* the key and a function modifier as the value, which will receive the
82+
* default attributes and return an array of attributes.
83+
*
84+
* Example:
85+
* { TAG: (attributes) => { attributes[] = 'attribute-name'; return attributes; } }
86+
*/
8187
options.elementAttributes = isPlainObject(options.elementAttributes) ? options.elementAttributes : {};
8288
options.safelistAttributes = Array.isArray(options.safelistAttributes) ? options.safelistAttributes : [];
8389
options.blocklistAttributes = Array.isArray(options.blocklistAttributes) ? options.blocklistAttributes : [];
8490

85-
// Merge customizer callback passed to lodash mergeWith
86-
// for merge attribute `props` and all attributes starting with `merge:`
87-
// @see https://lodash.com/docs/4.17.15#mergeWith
91+
/**
92+
* Merge customizer callback passed to `lodash.mergeWith` for merging
93+
* attribute `props` and all attributes starting with `merge:`.
94+
*
95+
* @see {@link https://lodash.com/docs/4.17.15#mergeWith|Lodash}
96+
*/
8897
options.mergeCustomizer = options.mergeCustomizer || ((objectValue, sourceValue) => {
8998
if (Array.isArray(objectValue)) {
9099
return objectValue.concat(sourceValue);
@@ -105,6 +114,7 @@ module.exports = (options = {}) => tree => {
105114

106115
if (!Array.isArray(options.matcher)) {
107116
options.matcher = [];
117+
108118
if (options.tagPrefix) {
109119
options.matcher.push({tag: options.tagPrefix});
110120
}
@@ -116,8 +126,10 @@ module.exports = (options = {}) => tree => {
116126

117127
options.folders = Array.isArray(options.folders) ? options.folders : [options.folders];
118128
options.namespaces = Array.isArray(options.namespaces) ? options.namespaces : [options.namespaces];
129+
119130
options.namespaces.forEach((namespace, index) => {
120131
options.namespaces[index].root = path.resolve(namespace.root);
132+
121133
if (namespace.fallback) {
122134
options.namespaces[index].fallback = path.resolve(namespace.fallback);
123135
}
@@ -148,14 +160,13 @@ module.exports = (options = {}) => tree => {
148160
};
149161
/* eslint-enable complexity */
150162

151-
// Used for reset aware props
163+
// Used to reset aware props
152164
let processCounter = 0;
153165

154166
/**
155167
* @param {Object} options Plugin options
156168
* @return {Object} PostHTML tree
157169
*/
158-
159170
function processTree(options) {
160171
const filledSlots = {};
161172

@@ -227,15 +238,18 @@ function processTree(options) {
227238

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

230-
// Remove attributes when value is 'null' or 'undefined'
231-
// so we can conditionally add an attribute by setting value to 'undefined' or 'null'.
241+
/**
242+
* Remove attributes when value is 'null' or 'undefined' so we can
243+
* conditionally add an attribute by setting the value to
244+
* 'undefined' or 'null'.
245+
*/
232246
walk.call(currentNode, node => {
233247
if (node && node.attrs) {
234-
each(node.attrs, (value, key) => {
235-
if (['undefined', 'null'].includes(value)) {
248+
for (const key in node.attrs) {
249+
if (node.attrs[key] === 'undefined' || node.attrs[key] === 'null') {
236250
delete node.attrs[key];
237251
}
238-
});
252+
}
239253
}
240254

241255
return node;

0 commit comments

Comments
 (0)