Skip to content

Commit

Permalink
Some additional features (#23)
Browse files Browse the repository at this point in the history
* Update deps + rename options for clarity

* Add typescript definitions

reshapeCustomElements(..) returns a plain 'Function' for now since the core reshape module doesn't include definitions yet

* Make it possible to locally override the default replacement tag

This may not seem like a useful feature at first (since we're typing both the class an tag name again), but in my opinion it helps convey *semantic* meaning rather than *syntactical* meaning when editing templates.

Possible improvement points:
- Not sure how I'm supposed to handle multiple attribute values here
- pretty-quick is being a bit weird, are you sure this is how you want it to be formatted?
- Maybe a different default attribute name?
- Maybe a shorter option name?
- The replacementTag option (#22) should probably be renamed to defaultReplacementTag

* Custom tag ↔ replacement tag map option

The README could probably be worded better

* Move the HTML in the README to it's own (syntax highlighted) section

* Add a generic blacklist

* Change the replacementTagMap to a more convenient format

* Make sure option names are consistent and update TS definitions

* Infer additionalTags from replacementTagMap
  • Loading branch information
HoldYourWaffle authored and jescalan committed Jun 3, 2019
1 parent 0ef19c2 commit 46df66e
Show file tree
Hide file tree
Showing 6 changed files with 358 additions and 131 deletions.
64 changes: 54 additions & 10 deletions README.md
Expand Up @@ -15,31 +15,75 @@ npm i reshape-custom-elements --save

## Usage

### Input HTML

```html
<my-component>
<my-text class="text">Text</my-text>

<!-- An actual HTML element defined in additionalTags -->
<label>Label</label>

<!-- Overriding the default replacement tag with a map -->
<my-footer>
Reshape is licensed under the MIT license
</my-footer>

<!-- Locally overriding the default replacement tag with an attribute -->
<my-text data-replacement="div">
This will get wrapped in a div instead of a span
</my-text>
</my-component>
```

### Reshape processing

```js
const reshape = require('reshape')
const customElements = require('reshape-custom-elements')

const html = `<my-component>
<my-text class="text">Text</my-text>
</my-component>`

reshape({plugins: [customElements({defaultTag: 'span'})]})
reshape({
plugins: [
customElements({
replacementTag: 'span',
additionalTags: ['label'],
replacementTagMap: {
footer: ['my-footer']
}
})
]
})
.process(html)
.then((res) => console.log(res.output()))
.then(res => console.log(res.output()))
```

### Output HTML

```html
<span class="my-component">
<span class="my-text text">Text</span>

<span class="label">Label</span>

<div class="my-text">
This will get wrapped in a div instead of a span
</div>

<footer class="my-footer">
Reshape is licensed under the MIT license
</footer>
</span>
```

## Options

| Name | Description | Default |
| ---- | ----------- | ------- |
| **defaultTag** | Tag used to replace the custom element tag name | `div` |
| **skipTags** | Array of tags to be processed despite being a normal html tag | `[]`
| Name | Description | Default |
| ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ |
| **defaultReplacementTag** | Tag used to replace the custom element tag name | `div` |
| **additionalTags** | Array of tags to be processed despite being a normal HTML tag. HTML tags that are used in the `replacementTagMap` will automatically be added to this list | `[]` |
| **blacklist** | Array of tags that should never be processed | `[]` |
| **replacementTagMap** | Object containing custom tag ↔ replacement tag mappings in the format: `ReplacedTag: [ customTag1, customTag2, ... ]`. Overrides `replacementTag` | `{}` |
| **replacementTagOverrideAttribute** | Attribute name that can be used to locally override the used replacement tag. Overrides `replacementTag` and `replacementTagMap` | `data-replacement` |

## License

Expand Down
124 changes: 124 additions & 0 deletions index.d.ts
@@ -0,0 +1,124 @@
export default function reshapeCustomElements(
options?: ReshapeCustomElementsOptions
): Function

export interface ReshapeCustomElementsOptions {
defaultReplacementTag?: string
additionalTags?: HtmlTags[]
blacklist?: string[]
replacementTagMap?: ReplacementTagMap
replacementTagAttr?: string
}

export type ReplacementTagMap = { [tag: string]: string[] }

type HtmlTags =
| 'a'
| 'abbr'
| 'address'
| 'area'
| 'article'
| 'aside'
| 'audio'
| 'b'
| 'base'
| 'bdi'
| 'bdo'
| 'blockquote'
| 'body'
| 'br'
| 'button'
| 'canvas'
| 'caption'
| 'cite'
| 'code'
| 'col'
| 'colgroup'
| 'datalist'
| 'dd'
| 'del'
| 'details'
| 'dfn'
| 'dialog'
| 'div'
| 'dl'
| 'dt'
| 'em'
| 'embed'
| 'fieldset'
| 'figcaption'
| 'figure'
| 'footer'
| 'form'
| 'h1'
| 'h2'
| 'h3'
| 'h4'
| 'h5'
| 'h6'
| 'head'
| 'header'
| 'hr'
| 'html'
| 'i'
| 'iframe'
| 'img'
| 'input'
| 'ins'
| 'kbd'
| 'keygen'
| 'label'
| 'legend'
| 'li'
| 'link'
| 'main'
| 'map'
| 'mark'
| 'menu'
| 'menuitem'
| 'meta'
| 'meter'
| 'nav'
| 'noscript'
| 'object'
| 'ol'
| 'optgroup'
| 'option'
| 'output'
| 'p'
| 'param'
| 'pre'
| 'progress'
| 'q'
| 'rp'
| 'rt'
| 'ruby'
| 's'
| 'samp'
| 'script'
| 'section'
| 'select'
| 'small'
| 'source'
| 'span'
| 'strong'
| 'style'
| 'sub'
| 'summary'
| 'sup'
| 'table'
| 'tbody'
| 'td'
| 'textarea'
| 'tfoot'
| 'th'
| 'thead'
| 'time'
| 'title'
| 'tr'
| 'track'
| 'u'
| 'ul'
| 'var'
| 'video'
| 'wbr'
66 changes: 55 additions & 11 deletions lib/index.js
@@ -1,18 +1,26 @@
const { modifyNodes } = require('reshape-plugin-util')

module.exports = function reshapeCustomElements(options = {}) {
const defaultTag = options.defaultTag || 'div'
const skipTags = options.skipTags || []
const defaultReplacementTag =
options.defaultReplacementTag || options.defaultTag || 'div'
const replacementTagMap = createLookupMap(options.replacementTagMap || {})
const additionalTags = (
options.additionalTags ||
options.skipTags ||
[]
).concat(inferAdditionalTags(replacementTagMap))
const blacklist = options.blacklist || []
const replacementTagAttr =
options.replacementTagOverrideAttribute || 'data-replacement'

return function(tree) {
return modifyNodes(
tree,
node => {
return (
node.type === 'tag' &&
(htmlTags.indexOf(node.name) < 0 || skipTags.indexOf(node.name) > -1)
)
},
node =>
node.type === 'tag' &&
blacklist.indexOf(node.name) < 0 &&
(htmlTags.indexOf(node.name) < 0 ||
additionalTags.indexOf(node.name) > -1),
node => {
// look for a class attribute
if (!node.attrs) {
Expand All @@ -24,7 +32,7 @@ module.exports = function reshapeCustomElements(options = {}) {

// if there's already the same class, return
if (node.attrs.class.find(n => n.content === node.name)) {
node.name = defaultTag
node.name = defaultReplacementTag
return node
}

Expand All @@ -44,14 +52,50 @@ module.exports = function reshapeCustomElements(options = {}) {
location: node.location
})

// set the name to the default and return
node.name = defaultTag
// find out the replacement tag
let replacementTag = defaultReplacementTag
if (
node.attrs[replacementTagAttr] &&
node.attrs[replacementTagAttr].length > 0
) {
// if there is a replacement override attribute, use it
replacementTag = node.attrs[replacementTagAttr][0].content
delete node.attrs[replacementTagAttr]
} else if (replacementTagMap[node.name]) {
// if there's a replacement tag mapping, use it
replacementTag = replacementTagMap[node.name]
}

// set the new tag name and return
node.name = replacementTag
return node
}
)
}
}

/** Converts the user-friendly array-based map into a regular (better performing) lookup map */
function createLookupMap(map) {
let output = {}
Object.entries(map).forEach(([replacementTag, array]) => {
array.forEach(customTag => {
output[customTag] = replacementTag
})
})
return output
}

/** Infer additional html tags to be parsed from the replacement tag map */
function inferAdditionalTags(replacementTagMap) {
const additionalTags = []
Object.keys(replacementTagMap)
.filter(tag => htmlTags.includes(tag))
.forEach(htmlTag => {
additionalTags.push(htmlTag)
})
return additionalTags
}

const htmlTags = [
'a',
'abbr',
Expand Down

0 comments on commit 46df66e

Please sign in to comment.