@codepatch/html
v1.0.1
Published
Transform HTML code the easy way
Downloads
7
Maintainers
Readme
@codepatch/html
Make small changes to your HTML code the easy way
Installation
npm install @codepatch/html
IMPORTANT: @codepatch/html
is an ESM-only package. Read more.
Motivation
@codepatch/html
is the ideal tool for programmatically making small & simple modifications to your HTML code in JavaScript. It works by parsing the code into an AST and then overriding parts of it. Learn more about the motivation behind Codepatch in the main repository.
As an introducing example, let's make all text in a document uppercase:
import { modify } from '@codepatch/html'
const code = `<p>hello<br>world!</p>`
const result = modify(code, (node, { source, override }) => {
if (node.type === 'text') {
override(source().toUpperCase())
}
})
console.log(result.code)
Output:
<p>HELLO<br />WORLD!</p>
Note that Codepatch is not a transpiler, so it's not ideal for large or complex changes, you'd want something like Cheerio for that.
Usage
How it Works
function modify(code, options = {}, manipulator)
Transform the code
string with the function manipulator
, returning an output object.
For every node in the AST, manipulator(node, helpers)
is called. The recursive walk is an
in-order, depth-first traversal, so children are handled before their parents. This makes it easier to write manipulators that perform nested transformations as transforming parents often requires transforming their children first anyway.
The modify()
return value is an object with two properties:
code
– contains the transformed source codemap
– holds the resulting source map object, as generated bymagic-string
Type casting a Codepatch result object to a string will return its source code
.
Pro Tip: Don't know what an HTML AST looks like? Have a look at astexplorer.net and select the "HTML" language with the "htmlparser2" parser to get an idea.
Options
All options are, as the name says, optional. If you want to provide an options object, its place is between the code
string and the manipulator
function.
htmlparser2
Options
Any options for the underlying htmlparser2
can be passed to options.parser.htmlparser2
:
const options = {
parser: {
htmlparser2: {
decodeEntities: false
}
}
}
modify('→', options, (node, helpers) => {
if (node.type === 'text') {
// node.data will be "→" instead of "→"
}
})
The same goes for domhandler
options which can be passed to options.parser.domhandler
Source Maps
Codepatch uses magic-string
under the hood to generate source maps for your code modifications. You can pass its source map options as options.sourceMap
:
const options = {
sourceMap: {
hires: true
}
}
modify(code, options, (node, helpers) => {
// Create a high-resolution source map
})
Helpers
The helpers
object passed to the manipulator
function exposes three methods. All of these methods handle the current AST node (the one that has been passed to the manipulator as its first argument).
However, each of these methods takes an AST node as an optional first parameter if you want to access other nodes.
Example:
modify('<p>hello world</p>', (node, { source }) => { if (node.type === 'tag') { // `node` refers to the p tag source() // returns "<p>hello world</p>" source(node.children[0]) // returns "hello world" } })
source()
Return the source code for the given node, including any modifications made to child nodes:
modify('hello', (node, { source, override }) => {
if (node.type === 'text') {
source() // returns "hello"
override('world')
source() // returns "world"
}
})
override(replacement)
Replace the source of the affected node with the replacement
string:
const result = modify('<p>hello<br>world</p>', (node, { source, override }) => {
if (node.type === 'tag' && node.name === 'p') {
const innerHtml = node.children.map(child => source(child)).join('')
override(innerHtml)
}
})
console.log(result.code)
Output:
hello<br>world
parent(levels = 1)
From the starting node, climb up the syntax tree levels
times. Getting an ancestor node of the program root yields undefined
.
modify('<p>hello <b>world</b></p>', (node, { parent }) => {
if (node.kind === 'text' && node.data === 'world') {
// `node` refers to the `world` text node
parent() // same as parent(1), refers to the `b` tag
parent(2) // refers to the `p` tag
parent(3) // refers to the document as a whole (root node)
parent(4) // yields `undefined`, same as parent(5), parent(6) etc.
}
})
External Helper Access
If you want to extract manipulation behavior into standalone functions, you can import the helpers directly from the @codepatch/html
package, where they are not bound to a specific node:
import { source, override } from '@codepatch/html'
// Standalone function, removes elements with no content
const removeEmpty = node => {
if (
Array.isArray(node.children) &&
node.children.every(node => source(node).length === 0)
) {
override(node, '')
}
}
// ...
import { modify } from '@codepatch/html'
const result = modify(
'<p>hello <strong><span></span></strong>world</p>',
node => {
removeEmpty(node)
}
)
console.log(result.code)
Output:
<p>hello world</p>
Asynchronous Manipulations
The manipulator
function may return a Promise. If it does, Codepatch will wait for that to resolve, making the whole modify()
function return a Promise resolving to the result object (instead of returning the result object directly):
const code = `
<div>
<embed type="replace" src="logo.svg" />
</div>
`
const deferredResult = modify(code, async (node, { source, override }) => {
if (
node.type === 'tag' &&
node.name === 'embed' &&
node.attribs.type === 'replace'
) {
// Replace all embed[type=replace] elements with their actual content
// Get the URL
const url = node.attribs.src
// Fetch the URL's contents
const contents = await fetch(url).then(response => response.text())
// Replace the embed with the fetched contents
override(contents)
}
})
// Result is not available immediately, we need to await it
deferredResult.then(result => {
console.log(result.code)
})
Output:
const content = `
<div>
<svg>...</svg>
</div>
`
Note: You have to return a Promise if you want to commit updates asynchronously. Once the manipulator function is done running, any
override()
calls originating from it will throw an error.
Related
@codepatch/html
is part of the Codepatch family of tools. Codepatch is a collection of tools that make it easy to programmatically make simple modifications to code of various languages.
Check out the Codepatch repository to find tools for other languages or information about how to write your own Codepatch modifier.