posthtml-component
Advanced tools
Comparing version
{ | ||
"name": "posthtml-component", | ||
"version": "1.0.0-beta.16", | ||
"version": "1.0.0", | ||
"description": "PostHTML Components Blade-like with slots, attributes as props and custom tag", | ||
@@ -5,0 +5,0 @@ "license": "MIT", |
158
readme.md
@@ -20,6 +20,6 @@ [![NPM][npm]][npm-url] | ||
This PostHTML plugin provides an HTML-friendly syntax for write components in your templates. | ||
If you are familiar with Blade, you will find similar syntax as this plugin is inspired by it. | ||
If you are familiar with Blade, React, Vue or similar, you will find familiar syntax as this plugin is inspired by them. | ||
See below a basic example, as code is worth a thousand words. | ||
> This plugin is still in early stage of development and the current API may change. | ||
**See also the first [PostHTML Bootstrap UI](https://github.com/thewebartisan7/posthtml-bootstrap-ui) using this plugin and check also the [starter template here](https://github.com/thewebartisan7/posthtml-bootstrap-ui-starter).** | ||
@@ -73,7 +73,7 @@ ## Basic example | ||
You may notice that our `src/button.html` component has a `type` and `class` attribute, and when we use the component in `src/index.html` we add type and class attribute. | ||
The result is that `type` is override, and `class` is merged. | ||
You may notice that the `src/button.html` component has a `type` and `class` attribute, and when we use the component in `src/index.html` we pass `type` and `class` attribute. | ||
The result is that `type` is override, and `class` is merged. | ||
By default `class` and `style` attributes are merged, while all others attribute are override. | ||
You can also override class and style attribute by prepending `override:` to the class attribute. Example: | ||
By default `class` and `style` attributes are merged, while all others attribute are override. | ||
You can also override `class` and `style` attributes by prepending `override:` to the class attribute. Example: | ||
@@ -87,39 +87,46 @@ ```html | ||
All attributes you pass to the component will be added to the first node of your component or to the node with an attribute names `attributes`, | ||
and only if they are not defined as `props` via `<script props>`. More details on this in [Attributes](#attributes) section. | ||
All attributes you pass to the component will be added to the first node of your component or to the node with an attribute names `attributes`, | ||
and only if they are not defined as `props` via `<script props>` or if they are not in the following file | ||
[valid-attributes.js](https://github.com/thewebartisan7/posthtml-components/blob/main/src/valid-attributes.js). | ||
You can also manage valid attributes via options. | ||
More details on this in [Attributes](#attributes) section. | ||
The `<yield>` tag is where your content will be injected. | ||
In next section you can find all available options and then examples for each features. | ||
In next section you can find all available options and then examples for each feature. | ||
See also the `docs-src` folder where you can find more examples. You can run `npm run build` to compile them. | ||
See also the `docs-src` folder where you can find more examples. | ||
You can run `npm run build` to compile them. | ||
## Options | ||
| Option | Default | Description | | ||
|:------------------------:|:--------------------------------------------:|:------------------------------------------------------------------------------------------------------------------------------| | ||
| **root** | `'./'` | String value as root path for components lookup. | | ||
| **folders** | `['']` | Array of additional multi folders path from `options.root` or any defined namespaces root, fallback or custom. | | ||
| **tagPrefix** | `x-` | String for tag prefix. The plugin will use RegExp with this string. | | ||
| **tag** | `false` | String or boolean value for component tag. Use this with `options.attribute`. Boolean only false. | | ||
| **attribute** | `src` | String value for component attribute for set path. | | ||
| **namespaces** | `[]` | Array of namespace's root path, fallback path and custom path for override. | | ||
| **namespaceSeparator** | `::` | String value for namespace separator to be used with tag name. Example `<x-namespace::button>` | | ||
| **fileExtension** | `html` | String value for file extension of the components used for retrieve x-tag file. | | ||
| **yield** | `yield` | String value for `<yield>` tag name. Where main content of component is injected. | | ||
| **slot** | `slot` | String value for `<slot>` tag name. Used with RegExp by appending `:` (example `<slot:slot-name>`). | | ||
| **fill** | `fill` | String value for `<fill>` tag name. Used with RegExp by appending `:` (example `<fill:slot-name>`). | | ||
| **slotSeparator** | `:` | String value used for separate `<slot>` and `<fill>` tag from their names. | | ||
| **push** | `push` | String value for `<push>` tag name. | | ||
| **stack** | `stack` | String value for `<stack>` tag name. | | ||
| **propsScriptAttribute** | `props` | String value used as attribute in `<script props>` parsed by the plugin to retrieve props of the component. | | ||
| **propsContext** | `props` | String value used as object name inside the script to process props before passed to the component. | | ||
| **propsAttribute** | `props` | String value for props attribute to define props as JSON. | | ||
| **propsSlot** | `props` | String value used to retrieve the props passed to slot via `$slots.slotName.props`. | | ||
| **expressions** | `{}` | Object to configure `posthtml-expressions`. You can pre-set locals or customize the delimiters for example. | | ||
| **plugins** | `[]` | PostHTML plugins to apply for every parsed components. | | ||
| **matcher** | `[{tag: options.tagPrefix}]` | Array of object used to match the tags. | | ||
| **attrsParserRules** | `{}` | Additional rules for attributes parser plugin. | | ||
| **strict** | `true` | Boolean value for enable or disable throw an exception. | | ||
| **mergeCustomizer** | `function` | Function callback passed to lodash `mergeWith` for merge `options.expressions.locals` and props passed via attribute `props`. | | ||
| **utilities** | `{merge: _.mergeWith, template: _.template}` | Object of utilities methods to be passed to `<script props>`. By default lodash `mergeWith` and `template`. | | ||
| Option | Default | Description | | ||
|:------------------------:|:---------------------------------------------:|:------------------------------------------------------------------------------------------------------------------------------| | ||
| **root** | `'./'` | String value as root path for components lookup. | | ||
| **folders** | `['']` | Array of additional multi folders path from `options.root` or any defined namespaces root, fallback or custom. | | ||
| **tagPrefix** | `x-` | String for tag prefix. The plugin will use RegExp with this string. | | ||
| **tag** | `false` | String or boolean value for component tag. Use this with `options.attribute`. Boolean only false. | | ||
| **attribute** | `src` | String value for component attribute for set path. | | ||
| **namespaces** | `[]` | Array of namespace's root path, fallback path and custom path for override. | | ||
| **namespaceSeparator** | `::` | String value for namespace separator to be used with tag name. Example `<x-namespace::button>` | | ||
| **fileExtension** | `html` | String value for file extension of the components used for retrieve x-tag file. | | ||
| **yield** | `yield` | String value for `<yield>` tag name. Where main content of component is injected. | | ||
| **slot** | `slot` | String value for `<slot>` tag name. Used with RegExp by appending `:` (example `<slot:slot-name>`). | | ||
| **fill** | `fill` | String value for `<fill>` tag name. Used with RegExp by appending `:` (example `<fill:slot-name>`). | | ||
| **slotSeparator** | `:` | String value used for separate `<slot>` and `<fill>` tag from their names. | | ||
| **push** | `push` | String value for `<push>` tag name. | | ||
| **stack** | `stack` | String value for `<stack>` tag name. | | ||
| **propsScriptAttribute** | `props` | String value used as attribute in `<script props>` parsed by the plugin to retrieve props of the component. | | ||
| **propsContext** | `props` | String value used as object name inside the script to process process before passed to the component. | | ||
| **propsAttribute** | `props` | String value for props attribute to define props as JSON. | | ||
| **propsSlot** | `props` | String value used to retrieve the props passed to slot via `$slots.slotName.props`. | | ||
| **expressions** | `{}` | Object to configure `posthtml-expressions`. You can pre-set locals or customize the delimiters for example. | | ||
| **plugins** | `[]` | PostHTML plugins to apply for every parsed components. | | ||
| **matcher** | `[{tag: options.tagPrefix}]` | Array of object used to match the tags. | | ||
| **attrsParserRules** | `{}` | Additional rules for attributes parser plugin. | | ||
| **strict** | `true` | Boolean value for enable or disable throw an exception. | | ||
| **mergeCustomizer** | `function` | Function callback passed to lodash `mergeWith` for merge `options.expressions.locals` and props passed via attribute `props`. | | ||
| **utilities** | `{merge: _.mergeWith, template: _.template}` | Object of utilities methods to be passed to `<script props>`. By default lodash `mergeWith` and `template`. | | ||
| **elementAttributes** | `{}` | An object with tag name and a function modifier of valid-attributes.js. | | ||
| **safelistAttributes** | `['data-*']` | An array of attributes name to be added to default valid attributes. | | ||
| **blacklistAttributes** | `[]` | An array of attributes name to be removed from default valid attributes. | | ||
@@ -219,3 +226,3 @@ ## Features | ||
For example let's suppose your main root is `./src` and then you have several folders where you have your components, for example `./src/components` and `./src/layouts`. | ||
You can setup the plugin like below: | ||
You can set up the plugin like below: | ||
@@ -236,3 +243,3 @@ ```js | ||
With namespaces you can define a top level root path to your components like shown in below example. | ||
With namespaces, you can define a top level root path to your components like shown in below example. | ||
It can be useful for handle custom theme, where you define a specific top level root, with fallback root when component it's not found, | ||
@@ -354,3 +361,3 @@ and a custom root for override, something like a child theme. | ||
By default the content is replaced, but you can also prepend or append the content, or keep the default content by not filling the slot. | ||
By default, the content is replaced, but you can also prepend or append the content, or keep the default content by not filling the slot. | ||
@@ -495,3 +502,3 @@ Add some default content in the component: | ||
By default the content is pushed in the stack in the given order. | ||
By default, the content is pushed in the stack in the given order. | ||
If you would like to prepend content onto the beginning of a stack, you should use the `prepend` attribute: | ||
@@ -563,7 +570,4 @@ | ||
If you don't add the props in `<script props>` inside your component, all props will be added as attributes to the first node of your component or to the node with attribute `attributes`. | ||
More details on this in the next section. | ||
In the `<script props>` you have access to passed props via object `props`, and you can add any logic you need inside it. | ||
So in the `<script props>` you have access to passed props via object `props`, and you can add any logic you need inside it. | ||
Create the component: | ||
@@ -612,3 +616,3 @@ | ||
You can also notice how the `class` attribute is merged with `class` attribute of the first node. Let's see in next section more about this. | ||
You can also notice how the `class` attribute is merged with `class` attribute of the first node. In the next section you will know more about this. | ||
@@ -687,6 +691,11 @@ You can change how attributes are merged with global props defined via options by passing a callback function used by lodash method [mergeWith](https://lodash.com/docs/4.17.15#mergeWith). | ||
Your can pass any attributes to your components and this will be added to the first node of your component, or to the node with an attribute named `attributes`. | ||
You can pass any attributes to your components and this will be added to the first node of your component, | ||
or to the node with an attribute named `attributes`. If you are familiar with VueJS this is the same as so called | ||
[fallthrough attribute](https://vuejs.org/guide/components/attrs.html), or with Laravel Blade is | ||
[component-attributes](https://laravel.com/docs/10.x/blade#component-attributes). | ||
By default `class` and `style` are merged with existing `class` and `style` attribute. | ||
All others attributes are override by default. | ||
Only attribute not defined as `props` will be used. | ||
Only attributes defined in [valid-attributes.js](https://github.com/thewebartisan7/posthtml-components/blob/main/src/valid-attributes.js) | ||
or not defined as `props` in the `<script props>`. | ||
@@ -723,4 +732,2 @@ As already seen in basic example: | ||
If you are familiar with Laravel Blade, this is also how Blade handle this. | ||
As said early, `class` and `style` are merged by default, if you want to override them, just prepend `override:` to the attribute name: | ||
@@ -740,3 +747,3 @@ | ||
If you want to use another node for add such attributes, then you can add the attribute `attributes` like shown below. | ||
If you want to use another node and not the first one, then you can add the attribute `attributes` like shown below. | ||
@@ -772,5 +779,52 @@ ```html | ||
### Advanced attributes configurations | ||
If default configurations for valid attributes are not right for you, then you can configure them as explained below. | ||
```js | ||
// index.js | ||
const { readFileSync, writeFileSync } = require('fs') | ||
const posthtml = require('posthtml') | ||
const components = require('posthtml-components') | ||
const options = { | ||
root: './src', | ||
// Add attributes to specific tag or override defaults | ||
elementAttributes: { | ||
DIV: (defaultAttributes) => { | ||
/* Add new one */ | ||
defaultAttributes.push('custom-attribute-name'); | ||
return defaultAttributes; | ||
}, | ||
DIV: (defaultAttributes) => { | ||
/* Override all */ | ||
defaultAttributes = ['custom-attribute-name', 'another-one']; | ||
return defaultAttributes; | ||
}, | ||
}, | ||
// Add attributes to all tags, use '*' as wildcard for attribute name that starts with | ||
safelistAttributes: [ | ||
'custom-attribute-name', | ||
'attribute-name-start-with-*' | ||
], | ||
// Remove attributes from all tags that support it | ||
blacklistAttributes: [ | ||
'role' | ||
] | ||
} | ||
posthtml(components(options)) | ||
.process(readFileSync('src/index.html', 'utf8')) | ||
.then((result) => writeFileSync('dist/index.html', result.html, 'utf8')) | ||
``` | ||
## Examples | ||
You can work with `<slot>` and `<fill>` or you can create component for each "block" of your component, and you can also support both of them. | ||
You can work with `<slot>` and `<fill>` or you can create component for each block of your component, and you can also support both of them. | ||
You can find an example of this inside `docs-src/components/modal`. Below is a short explanation about the both approach. | ||
@@ -777,0 +831,0 @@ |
@@ -47,3 +47,3 @@ 'use strict'; | ||
if (!componentPath) { | ||
throw new Error(`[components] For the tag ${tag} was not found any template in defined root path ${options.folders.join(', ')}`); | ||
throw new Error(`[components] <${tag}> could not find ${fileNameFromTag} in the defined root paths (${options.folders.join(', ')})`); | ||
} | ||
@@ -67,3 +67,3 @@ | ||
if (!namespaceOption) { | ||
throw new Error(`[components] Unknown component namespace ${namespace}.`); | ||
throw new Error(`[components] Unknown component namespace: ${namespace}.`); | ||
} | ||
@@ -89,3 +89,3 @@ | ||
if (!componentPath && options.strict) { | ||
throw new Error(`[components] For the tag ${tag} was not found any template in the defined namespace's base path ${namespaceOption.root}.`); | ||
throw new Error(`[components] <${tag}> could not find ${fileNameFromTag} in the defined namespace base path ${namespaceOption.root}`); | ||
} | ||
@@ -92,0 +92,0 @@ |
@@ -31,2 +31,3 @@ 'use strict'; | ||
const assign = require('lodash/assign'); | ||
const isPlainObject = require('lodash/isPlainObject'); | ||
@@ -65,2 +66,3 @@ /* eslint-disable complexity */ | ||
has, | ||
isPlainObject, | ||
isObject: isObjectLike, | ||
@@ -76,2 +78,9 @@ isArray, | ||
}; | ||
// 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; } } | ||
options.elementAttributes = isPlainObject(options.elementAttributes) ? options.elementAttributes : {}; | ||
options.safelistAttributes = Array.isArray(options.safelistAttributes) ? options.safelistAttributes : []; | ||
options.blacklistAttributes = Array.isArray(options.blacklistAttributes) ? options.blacklistAttributes : []; | ||
@@ -267,2 +276,5 @@ // Merge customizer callback passed to lodash mergeWith | ||
// Delete attribute used as path | ||
delete currentNode.attrs[options.attribute]; | ||
return componentPath; | ||
@@ -269,0 +281,0 @@ } |
@@ -6,5 +6,7 @@ 'use strict'; | ||
const styleToObject = require('style-to-object'); | ||
const omit = require('lodash/omit'); | ||
const validAttributes = require('./valid-attributes'); | ||
const keys = require('lodash/keys'); | ||
const union = require('lodash/union'); | ||
const pick = require('lodash/pick'); | ||
const difference = require('lodash/difference'); | ||
const each = require('lodash/each'); | ||
@@ -15,2 +17,3 @@ const has = require('lodash/has'); | ||
const isObject = require('lodash/isObject'); | ||
const isEmpty = require('lodash/isEmpty'); | ||
@@ -48,4 +51,40 @@ /** | ||
const mainNodeAttributes = omit(attributes, union(keys(props), [options.attribute], keys(aware), keys(options.props), ['$slots'])); | ||
// Merge elementAttributes and blacklistAttributes with options provided | ||
validAttributes.blacklistAttributes = union(validAttributes.blacklistAttributes, options.blacklistAttributes); | ||
validAttributes.safelistAttributes = union(validAttributes.safelistAttributes, options.safelistAttributes); | ||
// Merge or override elementAttributes from options provided | ||
if (!isEmpty(options.elementAttributes)) { | ||
each(options.elementAttributes, (modifier, tagName) => { | ||
if (typeof modifier === 'function' && isString(tagName)) { | ||
tagName = tagName.toUpperCase(); | ||
const attributes = modifier(validAttributes.elementAttributes[tagName]); | ||
if (Array.isArray(attributes)) { | ||
validAttributes.elementAttributes[tagName] = attributes; | ||
} | ||
} | ||
}); | ||
} | ||
// Attributes to be excluded | ||
const excludeAttributes = union(validAttributes.blacklistAttributes, keys(props), keys(aware), keys(options.props), ['$slots']); | ||
// All valid HTML attributes for the main element | ||
const allValidElementAttributes = isString(mainNode.tag) && has(validAttributes.elementAttributes, mainNode.tag.toUpperCase()) ? validAttributes.elementAttributes[mainNode.tag.toUpperCase()] : []; | ||
// Valid HTML attributes without the excluded | ||
const validElementAttributes = difference(allValidElementAttributes, excludeAttributes); | ||
// Add override attributes | ||
validElementAttributes.push('override:style'); | ||
validElementAttributes.push('override:class'); | ||
// Pick valid attributes from passed | ||
const mainNodeAttributes = pick(attributes, validElementAttributes); | ||
// Get additional specified attributes | ||
each(attributes, (value, attr) => { | ||
each(validAttributes.safelistAttributes, additionalAttr => { | ||
if (additionalAttr === attr || (additionalAttr.endsWith('*') && attr.startsWith(additionalAttr.replace('*', '')))) { | ||
mainNodeAttributes[attr] = value; | ||
} | ||
}); | ||
}); | ||
each(mainNodeAttributes, (value, key) => { | ||
@@ -52,0 +91,0 @@ if (['class', 'style'].includes(key)) { |
@@ -5,2 +5,3 @@ 'use strict'; | ||
const {render} = require('posthtml-render'); | ||
const get = require('lodash/get'); | ||
@@ -17,4 +18,8 @@ /** | ||
match.call(tree, {tag: push}, pushNode => { | ||
if (get(pushNode, 'attrs.name') === '') { | ||
throw new Error(`[components] <${push}> tag requires a value for the "name" attribute.`); | ||
} | ||
if (!pushNode.attrs || !pushNode.attrs.name) { | ||
throw new Error(`[components] Push <${push}> tag must have an attribute "name".`); | ||
throw new Error(`[components] <${push}> tag requires a "name" attribute.`); | ||
} | ||
@@ -21,0 +26,0 @@ |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
121827
115.03%12
9.09%4276
539.16%1
-50%1012
5.64%8
33.33%1
Infinity%