posthtml-component 2.0.0 → 2.1.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +49 -44
- package/package.json +2 -2
- package/src/find-path.js +77 -47
- package/src/index.js +28 -14
- package/src/process-attributes.js +19 -16
- package/src/process-props.js +23 -14
- package/src/process-slots.js +7 -6
package/README.md
CHANGED
|
@@ -58,7 +58,7 @@ Init PostHTML:
|
|
|
58
58
|
```js
|
|
59
59
|
// index.js
|
|
60
60
|
const posthtml = require('posthtml')
|
|
61
|
-
const components = require('posthtml-
|
|
61
|
+
const components = require('posthtml-component')
|
|
62
62
|
const { readFileSync, writeFileSync } = require('node:fs')
|
|
63
63
|
|
|
64
64
|
posthtml([
|
|
@@ -117,37 +117,37 @@ You can clone this repo and run `npm run build` to compile them.
|
|
|
117
117
|
|
|
118
118
|
## Options
|
|
119
119
|
|
|
120
|
-
| Name | Type
|
|
121
|
-
|
|
122
|
-
| **root** | `String`
|
|
123
|
-
| **folders** | `String[]`
|
|
124
|
-
| **
|
|
125
|
-
| **
|
|
126
|
-
| **
|
|
127
|
-
| **
|
|
128
|
-
| **
|
|
129
|
-
| **
|
|
130
|
-
| **yield** | `String`
|
|
131
|
-
| **slot** | `String`
|
|
132
|
-
| **fill** | `String`
|
|
133
|
-
| **slotSeparator** | `String`
|
|
134
|
-
| **
|
|
135
|
-
| **
|
|
136
|
-
| **propsScriptAttribute** | `String`
|
|
137
|
-
| **propsContext** | `String`
|
|
138
|
-
| **propsAttribute** | `String`
|
|
139
|
-
| **propsSlot** | `String`
|
|
140
|
-
| **parserOptions** | `Object`
|
|
141
|
-
| **expressions** | `Object`
|
|
142
|
-
| **plugins** | `Array`
|
|
143
|
-
| **matcher** | `Object`
|
|
144
|
-
| **attrsParserRules** | `Object`
|
|
145
|
-
| **strict** | `Boolean`
|
|
146
|
-
| **mergeCustomizer** | `Function`
|
|
147
|
-
| **utilities** | `Object`
|
|
148
|
-
| **elementAttributes** | `Object`
|
|
149
|
-
| **safelistAttributes** | `String[]`
|
|
150
|
-
| **blocklistAttributes** | `String[]`
|
|
120
|
+
| Name | Type | Default | Description |
|
|
121
|
+
|--------------------------|--------------------|----------------------------------------------|----------------------------------------------------------------------------------|
|
|
122
|
+
| **root** | `String` | `'./'` | Root path where to look for components. |
|
|
123
|
+
| **folders** | `String[]` | `['']` | Array of paths relative to `options.root` or defined namespaces. |
|
|
124
|
+
| **fileExtension** | `String\|String[]` | `'html'` | Component file extensions to look for. |
|
|
125
|
+
| **tagPrefix** | `String` | `'x-'` | Tag prefix. |
|
|
126
|
+
| **tag** | `String\|Boolean` | `false` | Component tag. Use with `options.attribute`. Boolean only `false`. |
|
|
127
|
+
| **attribute** | `String` | `'src'` | Attribute to use for defining path to component file. |
|
|
128
|
+
| **namespaces** | `String[]` | `[]` | Array of namespace root paths, fallback paths, and custom override paths. |
|
|
129
|
+
| **namespaceSeparator** | `String` | `'::'` | Namespace separator for tag names. |
|
|
130
|
+
| **yield** | `String` | `'yield'` | Tag name for injecting main component content. |
|
|
131
|
+
| **slot** | `String` | `'slot'` | Tag name for [slots](#slots) |
|
|
132
|
+
| **fill** | `String` | `'fill'` | Tag name for filling slots. |
|
|
133
|
+
| **slotSeparator** | `String` | `':'` | Name separator for `<slot>` and `<fill>` tags. |
|
|
134
|
+
| **stack** | `String` | `'stack'` | Tag name for [`<stack>`](#stacks). |
|
|
135
|
+
| **push** | `String` | `'push'` | Tag name for `<push>`. |
|
|
136
|
+
| **propsScriptAttribute** | `String` | `'props'` | Attribute in `<script props>` for retrieving [component props](#props). |
|
|
137
|
+
| **propsContext** | `String` | `'props'` | Name of the object inside the script for processing props. |
|
|
138
|
+
| **propsAttribute** | `String` | `'props'` | Attribute name to define props as JSON on a component tag. |
|
|
139
|
+
| **propsSlot** | `String` | `'props'` | Used to retrieve props passed to slot via `$slots.slotName.props`. |
|
|
140
|
+
| **parserOptions** | `Object` | `{recognizeSelfClosing: true}` | Pass options to `posthtml-parser`. |
|
|
141
|
+
| **expressions** | `Object` | `{}` | Pass options to `posthtml-expressions`. |
|
|
142
|
+
| **plugins** | `Array` | `[]` | PostHTML plugins to apply to every parsed component. |
|
|
143
|
+
| **matcher** | `Object` | `[{tag: options.tagPrefix}]` | Array of objects used to match tags. |
|
|
144
|
+
| **attrsParserRules** | `Object` | `{}` | Additional rules for attributes parser plugin. |
|
|
145
|
+
| **strict** | `Boolean` | `true` | Toggle exception throwing. |
|
|
146
|
+
| **mergeCustomizer** | `Function` | `function` | Callback for lodash `mergeWith` to merge `options.expressions.locals` and props. |
|
|
147
|
+
| **utilities** | `Object` | `{merge: _.mergeWith, template: _.template}` | Utility methods passed to `<script props>`. |
|
|
148
|
+
| **elementAttributes** | `Object` | `{}` | Object with tag names and function modifiers of `valid-attributes.js`. |
|
|
149
|
+
| **safelistAttributes** | `String[]` | `['data-*']` | Array of attribute names to add to default valid attributes. |
|
|
150
|
+
| **blocklistAttributes** | `String[]` | `[]` | Array of attribute names to remove from default valid attributes. |
|
|
151
151
|
|
|
152
152
|
## Features
|
|
153
153
|
|
|
@@ -155,9 +155,9 @@ You can clone this repo and run `npm run build` to compile them.
|
|
|
155
155
|
|
|
156
156
|
You can use the components in multiple ways, or also a combination of them.
|
|
157
157
|
|
|
158
|
-
If you to use components as 'includes', you
|
|
158
|
+
If you want to use components as 'includes', you can define tag and `src` attribute names.
|
|
159
159
|
|
|
160
|
-
Using our previous button component example, we can define the tag and attribute names and then use it
|
|
160
|
+
Using our previous button component example, we can define the tag and attribute names and then use it like this:
|
|
161
161
|
|
|
162
162
|
```hbs
|
|
163
163
|
<!-- src/index.html -->
|
|
@@ -174,7 +174,7 @@ Init PostHTML:
|
|
|
174
174
|
// index.js
|
|
175
175
|
|
|
176
176
|
require('posthtml')(
|
|
177
|
-
require('posthtml-
|
|
177
|
+
require('posthtml-component')({
|
|
178
178
|
root: './src',
|
|
179
179
|
tag: 'component',
|
|
180
180
|
attribute: 'src'
|
|
@@ -183,16 +183,20 @@ require('posthtml')(
|
|
|
183
183
|
.then(/* ... */)
|
|
184
184
|
```
|
|
185
185
|
|
|
186
|
-
If you need more control over tag matching, you
|
|
186
|
+
If you need more control over tag matching, you may pass an array of matcher or single object via `options.matcher`:
|
|
187
187
|
|
|
188
188
|
```js
|
|
189
189
|
// index.js
|
|
190
190
|
const options = {
|
|
191
191
|
root: './src',
|
|
192
|
-
matcher: [
|
|
192
|
+
matcher: [
|
|
193
|
+
{tag: 'a-tag'},
|
|
194
|
+
{tag: 'another-one'},
|
|
195
|
+
{tag: new RegExp(`^app-`, 'i')},
|
|
196
|
+
]
|
|
193
197
|
};
|
|
194
198
|
|
|
195
|
-
require('posthtml')(require('posthtml-
|
|
199
|
+
require('posthtml')(require('posthtml-component')(options))
|
|
196
200
|
.process(/* ... */)
|
|
197
201
|
.then(/* ... */)
|
|
198
202
|
```
|
|
@@ -208,7 +212,7 @@ const options = {
|
|
|
208
212
|
tagPrefix: 'x-'
|
|
209
213
|
};
|
|
210
214
|
|
|
211
|
-
require('posthtml')(require('posthtml-
|
|
215
|
+
require('posthtml')(require('posthtml-component')(options))
|
|
212
216
|
.process(/* ... */)
|
|
213
217
|
.then(/* ... */)
|
|
214
218
|
```
|
|
@@ -254,12 +258,13 @@ const options = {
|
|
|
254
258
|
parserOptions: { decodeEntities: true }
|
|
255
259
|
};
|
|
256
260
|
|
|
257
|
-
require('posthtml')(require('posthtml-
|
|
261
|
+
require('posthtml')(require('posthtml-component')(options))
|
|
258
262
|
.process('some HTML', options.parserOptions)
|
|
259
263
|
.then(/* ... */)
|
|
260
264
|
```
|
|
261
265
|
|
|
262
|
-
|
|
266
|
+
> [!IMPORTANT]
|
|
267
|
+
> The `parserOptions` that you pass to the plugin must also be passed in the `process` method in your code, otherwise your PostHTML build will use `posthtml-parser` defaults and will override anything you've passed to `posthtml-component`.
|
|
263
268
|
|
|
264
269
|
#### Self-closing tags
|
|
265
270
|
|
|
@@ -267,7 +272,7 @@ The plugin supports self-closing tags by default, but you need to make sure to e
|
|
|
267
272
|
|
|
268
273
|
```js
|
|
269
274
|
// index.js
|
|
270
|
-
require('posthtml')(require('posthtml-
|
|
275
|
+
require('posthtml')(require('posthtml-component')({root: './src'}))
|
|
271
276
|
.process('your HTML...', {recognizeSelfClosing: true})
|
|
272
277
|
.then(/* ... */)
|
|
273
278
|
```
|
|
@@ -287,7 +292,7 @@ const options = {
|
|
|
287
292
|
folders: ['components', 'layouts']
|
|
288
293
|
};
|
|
289
294
|
|
|
290
|
-
require('posthtml')(require('posthtml-
|
|
295
|
+
require('posthtml')(require('posthtml-component')(options))
|
|
291
296
|
.process(/* ... */)
|
|
292
297
|
.then(/* ... */)
|
|
293
298
|
```
|
|
@@ -826,7 +831,7 @@ If default configurations for valid attributes are not right for you, you may co
|
|
|
826
831
|
const { readFileSync, writeFileSync } = require('fs')
|
|
827
832
|
|
|
828
833
|
const posthtml = require('posthtml')
|
|
829
|
-
const components = require('posthtml-
|
|
834
|
+
const components = require('posthtml-component')
|
|
830
835
|
|
|
831
836
|
const options = {
|
|
832
837
|
root: './src',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "posthtml-component",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.1.0-beta.1",
|
|
4
4
|
"description": "Laravel Blade-inspired components for PostHTML with slots, attributes as props, custom tags and more.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": "posthtml/posthtml-components",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"style-to-object": "^1.0.6"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
|
-
"@biomejs/biome": "1.
|
|
42
|
+
"@biomejs/biome": "1.9.4",
|
|
43
43
|
"@vitest/coverage-v8": "^2.0.4",
|
|
44
44
|
"conventional-changelog-cli": "^5.0.0",
|
|
45
45
|
"highlight.js": "^11.6.0",
|
package/src/find-path.js
CHANGED
|
@@ -13,16 +13,20 @@ const folderSeparator = '.';
|
|
|
13
13
|
* @return {String|boolean} Full path or boolean false
|
|
14
14
|
*/
|
|
15
15
|
module.exports = (tag, options) => {
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
+
)
|
|
21
25
|
|
|
22
26
|
try {
|
|
23
|
-
return tag.includes(options.namespaceSeparator)
|
|
24
|
-
searchInNamespaces(tag,
|
|
25
|
-
searchInFolders(tag,
|
|
27
|
+
return tag.includes(options.namespaceSeparator)
|
|
28
|
+
? searchInNamespaces(tag, fileNamesFromTag, options)
|
|
29
|
+
: searchInFolders(tag, fileNamesFromTag, options);
|
|
26
30
|
} catch (error) {
|
|
27
31
|
if (options.strict) {
|
|
28
32
|
throw new Error(error.message);
|
|
@@ -36,15 +40,17 @@ module.exports = (tag, options) => {
|
|
|
36
40
|
* Search component file in root
|
|
37
41
|
*
|
|
38
42
|
* @param {String} tag [tag name]
|
|
39
|
-
* @param {
|
|
43
|
+
* @param {Array} fileNamesFromTag [filename converted from tag name]
|
|
40
44
|
* @param {Object} options [posthtml options]
|
|
41
45
|
* @return {String|boolean} [custom tag root where the module is found]
|
|
42
46
|
*/
|
|
43
|
-
function searchInFolders(tag,
|
|
44
|
-
const componentPath = search(options.root, options.folders,
|
|
47
|
+
function searchInFolders(tag, fileNamesFromTag, options) {
|
|
48
|
+
const componentPath = search(options.root, options.folders, fileNamesFromTag, options.fileExtension);
|
|
45
49
|
|
|
46
50
|
if (!componentPath) {
|
|
47
|
-
throw new Error(
|
|
51
|
+
throw new Error(
|
|
52
|
+
`[components] <${tag}> could not find ${fileNamesFromTag} in the defined root paths (${options.folders.join(', ')})`
|
|
53
|
+
);
|
|
48
54
|
}
|
|
49
55
|
|
|
50
56
|
return componentPath;
|
|
@@ -54,65 +60,89 @@ function searchInFolders(tag, fileNameFromTag, options) {
|
|
|
54
60
|
* Search component file within all defined namespaces
|
|
55
61
|
*
|
|
56
62
|
* @param {String} tag [tag name with namespace]
|
|
57
|
-
* @param {
|
|
58
|
-
* @param {String} fileNameFromTag [filename converted from tag name]
|
|
63
|
+
* @param {Array} namespaceAndFileNames Array of [namespace]::[filename]
|
|
59
64
|
* @param {Object} options [posthtml options]
|
|
60
65
|
* @return {String|boolean} [custom tag root where the module is found]
|
|
61
66
|
*/
|
|
62
|
-
function searchInNamespaces(tag,
|
|
63
|
-
|
|
67
|
+
function searchInNamespaces(tag, namespaceAndFileNames, options) {
|
|
68
|
+
let result = '';
|
|
64
69
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}
|
|
70
|
+
for (const namespaceAndFileName of namespaceAndFileNames) {
|
|
71
|
+
const [namespace, fileNameFromTag] = namespaceAndFileName.split('::');
|
|
68
72
|
|
|
69
|
-
|
|
73
|
+
const namespaceOption = options.namespaces.find(n => n.name === namespace.replace(options.tagPrefix, ''));
|
|
70
74
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
}
|
|
75
|
+
if (!namespaceOption) {
|
|
76
|
+
throw new Error(`[components] Unknown component namespace: ${namespace}.`);
|
|
77
|
+
}
|
|
75
78
|
|
|
76
|
-
|
|
77
|
-
if (!componentPath) {
|
|
78
|
-
componentPath = search(namespaceOption.root, options.folders, fileNameFromTag, options.fileExtension);
|
|
79
|
-
}
|
|
79
|
+
let componentPath;
|
|
80
80
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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
|
+
}
|
|
85
95
|
|
|
86
|
-
|
|
87
|
-
|
|
96
|
+
if (!componentPath) {
|
|
97
|
+
throw new Error(`[components] <${tag}> could not find ${fileNameFromTag} in the defined namespace paths.`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
result = componentPath;
|
|
88
101
|
}
|
|
89
102
|
|
|
90
|
-
return
|
|
103
|
+
return result;
|
|
91
104
|
}
|
|
92
105
|
|
|
93
106
|
/**
|
|
94
|
-
* Main
|
|
107
|
+
* Main component file search function
|
|
95
108
|
*
|
|
96
|
-
* @param {String} root Base root or namespace root from options
|
|
97
|
-
* @param {Array} folders Folders from options
|
|
98
|
-
* @param {
|
|
99
|
-
* @param {
|
|
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`
|
|
100
113
|
* @return {String|boolean} [custom tag root where the module is found]
|
|
101
114
|
*/
|
|
102
|
-
function search(root, folders,
|
|
115
|
+
function search(root, folders, fileNames, extensions) {
|
|
103
116
|
let componentPath;
|
|
117
|
+
let componentFound = false;
|
|
104
118
|
|
|
105
|
-
|
|
106
|
-
componentPath = path.join(path.resolve(root, folder), fileName);
|
|
107
|
-
return existsSync(componentPath);
|
|
108
|
-
});
|
|
119
|
+
fileNames = Array.isArray(fileNames) ? fileNames : [fileNames];
|
|
109
120
|
|
|
110
|
-
|
|
111
|
-
fileName = fileName.replace(`.${extension}`, `${path.sep}index.${extension}`);
|
|
121
|
+
for (const fileName of fileNames) {
|
|
112
122
|
componentFound = folders.some(folder => {
|
|
113
123
|
componentPath = path.join(path.resolve(root, folder), fileName);
|
|
124
|
+
|
|
114
125
|
return existsSync(componentPath);
|
|
115
126
|
});
|
|
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
|
+
}
|
|
116
146
|
}
|
|
117
147
|
|
|
118
148
|
return componentFound ? componentPath : false;
|
package/src/index.js
CHANGED
|
@@ -74,17 +74,26 @@ module.exports = (options = {}) => tree => {
|
|
|
74
74
|
uniqueId,
|
|
75
75
|
isEnabled: prop => prop === true || prop === ''
|
|
76
76
|
};
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
+
*/
|
|
81
87
|
options.elementAttributes = isPlainObject(options.elementAttributes) ? options.elementAttributes : {};
|
|
82
88
|
options.safelistAttributes = Array.isArray(options.safelistAttributes) ? options.safelistAttributes : [];
|
|
83
89
|
options.blocklistAttributes = Array.isArray(options.blocklistAttributes) ? options.blocklistAttributes : [];
|
|
84
90
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|
+
*/
|
|
88
97
|
options.mergeCustomizer = options.mergeCustomizer || ((objectValue, sourceValue) => {
|
|
89
98
|
if (Array.isArray(objectValue)) {
|
|
90
99
|
return objectValue.concat(sourceValue);
|
|
@@ -105,6 +114,7 @@ module.exports = (options = {}) => tree => {
|
|
|
105
114
|
|
|
106
115
|
if (!Array.isArray(options.matcher)) {
|
|
107
116
|
options.matcher = [];
|
|
117
|
+
|
|
108
118
|
if (options.tagPrefix) {
|
|
109
119
|
options.matcher.push({tag: options.tagPrefix});
|
|
110
120
|
}
|
|
@@ -116,8 +126,10 @@ module.exports = (options = {}) => tree => {
|
|
|
116
126
|
|
|
117
127
|
options.folders = Array.isArray(options.folders) ? options.folders : [options.folders];
|
|
118
128
|
options.namespaces = Array.isArray(options.namespaces) ? options.namespaces : [options.namespaces];
|
|
129
|
+
|
|
119
130
|
options.namespaces.forEach((namespace, index) => {
|
|
120
131
|
options.namespaces[index].root = path.resolve(namespace.root);
|
|
132
|
+
|
|
121
133
|
if (namespace.fallback) {
|
|
122
134
|
options.namespaces[index].fallback = path.resolve(namespace.fallback);
|
|
123
135
|
}
|
|
@@ -148,14 +160,13 @@ module.exports = (options = {}) => tree => {
|
|
|
148
160
|
};
|
|
149
161
|
/* eslint-enable complexity */
|
|
150
162
|
|
|
151
|
-
// Used
|
|
163
|
+
// Used to reset aware props
|
|
152
164
|
let processCounter = 0;
|
|
153
165
|
|
|
154
166
|
/**
|
|
155
167
|
* @param {Object} options Plugin options
|
|
156
168
|
* @return {Object} PostHTML tree
|
|
157
169
|
*/
|
|
158
|
-
|
|
159
170
|
function processTree(options) {
|
|
160
171
|
const filledSlots = {};
|
|
161
172
|
|
|
@@ -227,15 +238,18 @@ function processTree(options) {
|
|
|
227
238
|
|
|
228
239
|
processAttributes(currentNode, attributes, props, options, aware);
|
|
229
240
|
|
|
230
|
-
|
|
231
|
-
|
|
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
|
+
*/
|
|
232
246
|
walk.call(currentNode, node => {
|
|
233
247
|
if (node && node.attrs) {
|
|
234
|
-
|
|
235
|
-
if (['undefined'
|
|
248
|
+
for (const key in node.attrs) {
|
|
249
|
+
if (node.attrs[key] === 'undefined' || node.attrs[key] === 'null') {
|
|
236
250
|
delete node.attrs[key];
|
|
237
251
|
}
|
|
238
|
-
}
|
|
252
|
+
}
|
|
239
253
|
}
|
|
240
254
|
|
|
241
255
|
return node;
|
|
@@ -8,7 +8,6 @@ const keys = require('lodash/keys');
|
|
|
8
8
|
const union = require('lodash/union');
|
|
9
9
|
const pick = require('lodash/pick');
|
|
10
10
|
const difference = require('lodash/difference');
|
|
11
|
-
const each = require('lodash/each');
|
|
12
11
|
const has = require('lodash/has');
|
|
13
12
|
const extend = require('lodash/extend');
|
|
14
13
|
const isString = require('lodash/isString');
|
|
@@ -52,15 +51,16 @@ module.exports = (currentNode, attributes, props, options, aware) => {
|
|
|
52
51
|
|
|
53
52
|
// Merge or override elementAttributes from options provided
|
|
54
53
|
if (!isEmpty(options.elementAttributes)) {
|
|
55
|
-
|
|
54
|
+
for (const tagName in options.elementAttributes) {
|
|
55
|
+
const modifier = options.elementAttributes[tagName];
|
|
56
56
|
if (typeof modifier === 'function' && isString(tagName)) {
|
|
57
|
-
|
|
58
|
-
const attributes = modifier(validAttributes.elementAttributes[
|
|
57
|
+
const upperTagName = tagName.toUpperCase();
|
|
58
|
+
const attributes = modifier(validAttributes.elementAttributes[upperTagName]);
|
|
59
59
|
if (Array.isArray(attributes)) {
|
|
60
|
-
validAttributes.elementAttributes[
|
|
60
|
+
validAttributes.elementAttributes[upperTagName] = attributes;
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
|
-
}
|
|
63
|
+
}
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
// Attributes to be excluded
|
|
@@ -76,15 +76,18 @@ module.exports = (currentNode, attributes, props, options, aware) => {
|
|
|
76
76
|
const mainNodeAttributes = pick(attributes, validElementAttributes);
|
|
77
77
|
|
|
78
78
|
// Get additional specified attributes
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
if (
|
|
82
|
-
|
|
79
|
+
for (const attr in attributes) {
|
|
80
|
+
for (const additionalAttr of validAttributes.safelistAttributes) {
|
|
81
|
+
if (
|
|
82
|
+
additionalAttr === attr
|
|
83
|
+
|| (additionalAttr.endsWith('*') && attr.startsWith(additionalAttr.replace('*', '')))
|
|
84
|
+
) {
|
|
85
|
+
mainNodeAttributes[attr] = attributes[attr];
|
|
83
86
|
}
|
|
84
|
-
}
|
|
85
|
-
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
86
89
|
|
|
87
|
-
|
|
90
|
+
for (const key in mainNodeAttributes) {
|
|
88
91
|
if (['class', 'style'].includes(key)) {
|
|
89
92
|
if (!has(nodeAttrs, key)) {
|
|
90
93
|
nodeAttrs[key] = key === 'class' ? [] : {};
|
|
@@ -100,16 +103,16 @@ module.exports = (currentNode, attributes, props, options, aware) => {
|
|
|
100
103
|
}
|
|
101
104
|
|
|
102
105
|
delete attributes[key];
|
|
103
|
-
}
|
|
106
|
+
}
|
|
104
107
|
|
|
105
108
|
// The plugin posthtml-attrs-parser compose() method expects a string,
|
|
106
109
|
// but since we are JSON parsing, values like "-1" become number -1.
|
|
107
110
|
// So below we convert non string values to string.
|
|
108
|
-
|
|
111
|
+
for (const key in nodeAttrs) {
|
|
109
112
|
if (key !== 'compose' && !isObject(nodeAttrs[key]) && !isString(nodeAttrs[key])) {
|
|
110
113
|
nodeAttrs[key] = nodeAttrs[key].toString();
|
|
111
114
|
}
|
|
112
|
-
}
|
|
115
|
+
}
|
|
113
116
|
|
|
114
117
|
mainNode.attrs = nodeAttrs.compose();
|
|
115
118
|
};
|
package/src/process-props.js
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const processScript = require('./process-script');
|
|
4
3
|
const pick = require('lodash/pick');
|
|
5
|
-
const each = require('lodash/each');
|
|
6
4
|
const assign = require('lodash/assign');
|
|
7
5
|
const mergeWith = require('lodash/mergeWith');
|
|
6
|
+
const processScript = require('./process-script');
|
|
8
7
|
|
|
9
8
|
const attributeTypes = ['aware', 'merge'];
|
|
10
9
|
|
|
@@ -23,31 +22,31 @@ module.exports = (currentNode, nextNode, filledSlots, options, componentPath, pr
|
|
|
23
22
|
let attributes = {...currentNode.attrs};
|
|
24
23
|
|
|
25
24
|
const attributesByTypeName = {};
|
|
26
|
-
|
|
25
|
+
for (const type of attributeTypes) {
|
|
27
26
|
attributesByTypeName[type] = [];
|
|
28
|
-
}
|
|
27
|
+
}
|
|
29
28
|
|
|
30
|
-
|
|
29
|
+
for (const key in attributes) {
|
|
31
30
|
let newKey = key;
|
|
32
|
-
|
|
31
|
+
for (const type of attributeTypes) {
|
|
33
32
|
if (key.startsWith(`${type}:`)) {
|
|
34
33
|
newKey = newKey.replace(`${type}:`, '');
|
|
35
34
|
attributesByTypeName[type].push(newKey);
|
|
36
35
|
}
|
|
37
|
-
}
|
|
36
|
+
}
|
|
38
37
|
|
|
39
38
|
if (newKey !== key) {
|
|
40
|
-
|
|
41
|
-
delete
|
|
39
|
+
attributes[newKey] = attributes[key];
|
|
40
|
+
delete attributes[key];
|
|
42
41
|
}
|
|
43
|
-
}
|
|
42
|
+
}
|
|
44
43
|
|
|
45
44
|
// Parse JSON attributes
|
|
46
|
-
|
|
45
|
+
for (const key in attributes) {
|
|
47
46
|
try {
|
|
48
|
-
|
|
47
|
+
attributes[key] = JSON.parse(attributes[key]);
|
|
49
48
|
} catch {}
|
|
50
|
-
}
|
|
49
|
+
}
|
|
51
50
|
|
|
52
51
|
// Merge or extend attribute props
|
|
53
52
|
if (attributes[options.propsAttribute]) {
|
|
@@ -65,7 +64,17 @@ module.exports = (currentNode, nextNode, filledSlots, options, componentPath, pr
|
|
|
65
64
|
attributes = mergeWith({}, options.expressions.locals, attributes, options.mergeCustomizer);
|
|
66
65
|
|
|
67
66
|
// Process props from <script props>
|
|
68
|
-
const {props} = processScript(
|
|
67
|
+
const {props} = processScript(
|
|
68
|
+
nextNode,
|
|
69
|
+
{
|
|
70
|
+
props: {...attributes},
|
|
71
|
+
$slots: filledSlots,
|
|
72
|
+
propsScriptAttribute: options.propsScriptAttribute,
|
|
73
|
+
propsContext: options.propsContext,
|
|
74
|
+
utilities: options.utilities
|
|
75
|
+
},
|
|
76
|
+
componentPath.replace(`.${options.fileExtension}`, '.js')
|
|
77
|
+
);
|
|
69
78
|
|
|
70
79
|
if (props) {
|
|
71
80
|
assign(attributes, props);
|
package/src/process-slots.js
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
const {match} = require('posthtml/lib/api');
|
|
4
4
|
const {render} = require('posthtml-render');
|
|
5
|
-
const each = require('lodash/each');
|
|
6
5
|
const omit = require('lodash/omit');
|
|
7
6
|
|
|
8
7
|
/**
|
|
@@ -25,11 +24,13 @@ function setFilledSlots(currentNode, filledSlots, {fill, slotSeparator, propsSlo
|
|
|
25
24
|
const props = omit(fillNode.attrs, ['append', 'prepend', 'aware']);
|
|
26
25
|
|
|
27
26
|
if (props) {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
27
|
+
for (const key in props) {
|
|
28
|
+
if (props.hasOwnProperty(key)) {
|
|
29
|
+
try {
|
|
30
|
+
props[key] = JSON.parse(props[key]);
|
|
31
|
+
} catch {}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
33
34
|
}
|
|
34
35
|
|
|
35
36
|
filledSlots[name] = {
|