This is a starter project for PostHTML plugins.
git clone https://github.com/posthtml/posthtml-plugin-starter.git
- Async and sync examples
- Supports both ESM and CJS
- Node.js 18+
- Tests with
vitest
- Linting with
biome
- Releases with
np
- CI with GitHub Actions
When writing an async PostHTML plugin, you must return a Promise
that resolves the current tree
:
export default (options = {}) => tree => {
// Accept options and set defaults
options.foo = options.foo || {}
return new Promise((resolve, reject) => {
try {
// do something async
// finally, resolve the promise by returning the tree
resolve(tree)
} catch (error) {
reject(error)
}
})
}
Note: async plugins can't be used in PostHTML's sync
mode.
Synchronous plugins just need to return the tree
.
You may use the tree.walk
method to traverse the tree
and handle nodes.
The handling of nodes can be a function that is passed to tree.walk
, and which must return the node:
export default (options = {}) => tree => {
options.foo = options.foo || {}
const handleNodes = node => {
// Write your code...
// Return the node
return node
}
return tree.walk(handleNodes)
}
The testing boilerplate includes a process()
method which accepts 4 parameters:
t
the test objectname
the file name of the fixture/expected files, excluding extensionoptions
any options to pass to the plugin when testinglog
a boolean that turns on logging to console
For example, imagine we're writing a test that uses /test/fixtures/skip-nodes.html
:
test('It skips nodes defined in `skipNodes` option', t => {
return process(t, 'skip-nodes', {skipNodes: ['a']}, true)
})
As you can see, the second parameter passed to the process()
method is the fixture file name, without the .html
extension.
To test errors thrown by your plugin, use the error()
method:
test('Syntax error', t => {
return error('syntax-error', err => {
expect(err.message).toBe('Invalid or unexpected token')
})
})
Just like before, the first parameter passed to error()
is the fixture file name, without the extension.
You may configure eslint
in .eslintrc
. See ESLint rules for options.
@vitest/coverage-v8
defaults are used, you may configure it.
The starter uses np
for publishing to npm, take a look at its configuration options.
When publishing your first release, leave "version": "0.0.0"
in package.json
- you will set it through np
's interactive UI.
GitHub Actions is used for continuous integration, and you can configure it by editing the .github/workflows/nodejs.yml
file.
A .github/dependabot.yml
config file is also included, to help automate dependency updates.
- update shield icon URLs at the end of this file
- edit (or remove) the issue template
- update
package.json
fields - update the
LICENSE
file
Delete all of the above text, including the separator below - what follows is some boilerplate for your plugin's README.md
.
Describe what your plugin does.
Optionally add a short before & after example, like so:
Input:
<div filter="uppercase">Test</div>
Output:
<div>TEST</div>
npm i posthtml posthtml-myplugin
Provide clear code samples showing how to use the plugin:
import posthtml from 'posthtml'
import myplugin from 'posthtml-myplugin'
posthtml([
myplugin()
])
.process('<div filter="uppercase">Test</div>')
.then(result => console.log(result.html))
Result:
<div>TEST</div>
Most PostHTML plugins use custom HTML syntax, like custom tag names or even custom attributes. If your plugin requires using custom markup, document it here.
For example:
Use the <uppercase>
tag to transform all text inside it:
<uppercase>Test</uppercase>
The tag is removed in the output.
Result:
TEST
You can use a filter by calling it as the value of the filter
attribute:
<div filter="uppercase">Test</div>
The filter
attribute is removed in the output.
Result:
<div>TEST</div>
If your plugin can be configured through options, explain what they do and how to use them. Make sure to specify what the defaults are.
For example:
Type: array
Default: []
Array of filter names to use. All other filters will be disabled.
import posthtml from 'posthtml'
import myplugin from 'posthtml-myplugin'
posthtml([
myplugin({
only: ['uppercase', 'slugify']
})
])
.process('<div filter="uppercase">Test</div>')
.then(result => console.log(result.html))
By default, this is set to an empty array, which means that all filters can be used.
If your plugin depends on third party libraries that require configuration, explain here what the user needs to do.