Skip to content

Commit f617171

Browse files
Alexander NortungAlexnortung
authored andcommitted
feat(plugins): added plugin system
1 parent 6574504 commit f617171

10 files changed

Lines changed: 548 additions & 62 deletions

File tree

README.md

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,14 @@ Write HTML → get Word, PDFs, spreadsheets, and more — all with one unified T
1515

1616
## How It Works
1717

18-
Below is a high-level overview of the conversion pipeline. The library processes the HTML input through optional middleware steps, parses it into a structured intermediate representation, and then delegates to an adapter to generate the desired output format.
18+
Below is a high-level overview of the conversion pipeline. The library processes the HTML input through optional plugin steps, parses it into a structured intermediate representation, and then delegates to an adapter to generate the desired output format.
1919

2020
![Conversion Pipeline Diagram](./static/img/conversion-pipeline.png)
2121

2222
The stages are:
2323

2424
- **Input**: Raw HTML input as a string.
25-
- **Middleware**: One or more middleware functions can inspect or transform the HTML string before parsing (e.g., sanitization, custom tags).
25+
- **Plugins**: `beforeParse` hooks can inspect or transform the HTML string before parsing, and `afterParse` hooks can transform parsed `DocumentElement[]`. Deprecated middleware still works through internal plugin adaptation.
2626
- **Parser**: Converts the (possibly modified) HTML string into an array of `DocumentElement` objects, representing a structured AST.
2727
- **Adapter**: Takes the parsed `DocumentElement[]` and renders it into the target format (e.g., DOCX, PDF, Markdown) via a registered adapter.
2828

@@ -38,7 +38,7 @@ The stages are:
3838
| **Style mapping engine** | Define your own css mappings for the adapters and set per‑format defaults |
3939
| **Custom tag handlers** | Override or extend how any HTML tag is parsed |
4040
| **Page sections & headers** | Use `<section class="page">`, `<section class="page-break">`, `<header>` and `<footer>` to control pages in DOCX |
41-
| **Middleware pipeline** | Transform or sanitise HTML before parsing |
41+
| **Plugin pipeline** | Transform HTML before parsing or transform `DocumentElement[]` after parsing |
4242

4343
---
4444

@@ -183,6 +183,46 @@ const elements = await converter.parse('<p>Some HTML</p>');
183183
console.log(elements); // => DocumentElement[]
184184
```
185185
186+
### Plugins
187+
188+
Plugins are the primary way to extend parsing.
189+
190+
```ts
191+
const converter = init({
192+
plugins: [
193+
{
194+
name: 'strip-scripts',
195+
beforeParse: async (html) =>
196+
html.replace(/<script[\s\S]*?>[\s\S]*?<\/script>/g, ''),
197+
},
198+
{
199+
name: 'mark-generated',
200+
afterParse: async (elements) =>
201+
elements.map((element) => ({
202+
...element,
203+
metadata: { ...element.metadata, generated: true },
204+
})),
205+
},
206+
],
207+
});
208+
```
209+
210+
The built-in `minify` plugin is enabled by default. Disable built-in plugins with `enableDefaultPlugins: false`.
211+
212+
Deprecated `middleware` and `clearMiddleware` still work:
213+
214+
- `middleware` entries are adapted into `beforeParse` plugins internally
215+
- `clearMiddleware: true` implies `enableDefaultPlugins: false`
216+
- explicit `enableDefaultPlugins` overrides that implication
217+
218+
You can also register plugins after construction:
219+
220+
```ts
221+
converter.usePlugin({
222+
beforeParse: async (html) => html.replace('Draft', 'Final'),
223+
});
224+
```
225+
186226
---
187227

188228
## 📚 Documentation & Demo

docs/docs/api/html-to-document.md

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,12 @@ converter
4949
Initialize a new [`Converter`](./types) instance.
5050

5151
- **options**: [`InitOptions`](./types) (optional)
52-
- `middleware?: [`Middleware`](./types)[]` – custom middleware functions.
52+
- `plugins?: [`Plugin`](./types)[]` – plugin hooks for pre-parse and post-parse transforms.
53+
- `enableDefaultPlugins?: boolean` – enable or disable built-in plugins.
54+
- `middleware?: [`Middleware`](./types)[]` – deprecated middleware compatibility layer.
5355
- `tags?: { tagHandlers?: [`TagHandlerObject`](./types)[]; defaultStyles?: ...; defaultAttributes?: ... }` – custom tag handlers and default tag options.
5456
- `adapters?: { defaultStyles?: ...; register?: { format: string; adapter: [`AdapterProvider`](./types); config?: object; createAdapter?: ... }[] }` – register adapters, customize construction per adapter, and pass adapter-specific config.
55-
- `clearMiddleware?: boolean`clear default middleware.
57+
- `clearMiddleware?: boolean`deprecated legacy switch that disables default plugins by implication.
5658
- `domParser?: [`IDOMParser`](./types)` – custom DOM parser implementation.
5759

5860
Returns: a configured [`Converter`](./types) instance.
@@ -62,6 +64,7 @@ Returns: a configured [`Converter`](./types) instance.
6264
Explore further customization using the links below:
6365

6466
- [Initialization](./init)
67+
- [Plugins](./plugins)
6568
- [Custom Tag Handlers](./tags)
6669
- [Middleware](./middleware)
6770
- [Style Mappings & Default Styles](./style-mappings)
@@ -78,6 +81,10 @@ Class for parsing and converting HTML to document formats.
7881
Create a Converter with raw options:
7982

8083
- `tags?: ...` – alias for `options.tags` in `init`.
84+
- `plugins?: [`Plugin`](./types)[]`
85+
- `enableDefaultPlugins?: boolean`
86+
- `middleware?: [`Middleware`](./types)[]` (deprecated)
87+
- `clearMiddleware?: boolean` (deprecated)
8188
- `adapters?: ...`
8289
- `registerAdapters?: { format: string; adapter: [`IDocumentConverter`](./types) }[]`
8390
- `domParser?: [`IDOMParser`](./types)`
@@ -107,6 +114,12 @@ Register a middleware function to process HTML before parsing.
107114

108115
- `mw`: [`Middleware`](./types) function.
109116

117+
##### usePlugin(plugin: [`Plugin`](./types)): void
118+
119+
Register a plugin after construction.
120+
121+
- `plugin`: [`Plugin`](./types) object.
122+
110123
##### registerConverter(name: string, converter: [`IDocumentConverter`](./types)): void
111124

112125
Register a custom document converter adapter.
@@ -121,6 +134,7 @@ Register a custom document converter adapter.
121134
| [`InitOptions`](./types) | Options for initializing the converter via `init`. |
122135
| [`ConverterOptions`](./types) | Internal options for the `Converter` constructor. |
123136
| [`Converter`](./types) | Main class for conversion and parsing. |
137+
| [`Plugin`](./types) | Optional `beforeParse` and `afterParse` hooks for extending the conversion pipeline. |
124138
| [`Middleware`](./types) | Asynchronous function taking an HTML string and returning a Promise of string. |
125139
| [`TagHandler`](./types) | Handler that processes an `HTMLElement` with optional `TagHandlerOptions` and returns a `DocumentElement` or an array of `DocumentElement`. |
126140
| [`TagHandlerObject`](./types) | `{ key: string; handler: TagHandler }` |

docs/docs/api/init.md

Lines changed: 63 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ sidebar_position: 2
77

88
# Initialization
99

10-
The `init` function is your main entry point to configure and initialize the converter engine. It returns a `Converter` instance that can parse HTML and convert it into document formats like DOCX, PDF, or Markdown. Through `init`, you can register custom adapters, tag handlers, middleware, and default styles to control how HTML is interpreted and styled.
10+
The `init` function is your main entry point to configure and initialize the converter engine. It returns a `Converter` instance that can parse HTML and convert it into document formats like DOCX, PDF, or Markdown. Through `init`, you can register custom adapters, tag handlers, plugins, and default styles to control how HTML is interpreted and styled.
1111

1212
## Quick Start
1313

@@ -42,13 +42,62 @@ declare function init(options?: InitOptions): Converter;
4242

4343
The `options` object conforms to the [`InitOptions`](./types) type and supports the following properties:
4444

45+
### `plugins?: Plugin[]`
46+
47+
Register one or more plugins for the converter pipeline.
48+
49+
- **Type:** [`Plugin`](./types)[]
50+
- **Default:** the built-in `minify` plugin is enabled unless disabled by `enableDefaultPlugins: false`, or implicitly by legacy `clearMiddleware: true`
51+
- **Hooks:**
52+
- `beforeParse?(html)` transforms the raw HTML string
53+
- `afterParse?(elements)` transforms the parsed `DocumentElement[]`
54+
- **Order:** plugins run in array order; all `beforeParse` hooks run before parsing and all `afterParse` hooks run after parsing
55+
- **Errors:** plugin failures fail fast and surface their original errors
56+
- **Example:**
57+
58+
```ts
59+
import { init } from 'html-to-document';
60+
61+
const converter = init({
62+
plugins: [
63+
{
64+
name: 'strip-scripts',
65+
beforeParse: async (html) =>
66+
html.replace(/<script[\s\S]*?>[\s\S]*?<\/script>/g, ''),
67+
},
68+
{
69+
name: 'mark-paragraphs',
70+
afterParse: async (elements) =>
71+
elements.map((element) =>
72+
element.type === 'paragraph'
73+
? {
74+
...element,
75+
metadata: { ...element.metadata, sanitized: true },
76+
}
77+
: element
78+
),
79+
},
80+
],
81+
});
82+
```
83+
84+
### `enableDefaultPlugins?: boolean`
85+
86+
Controls whether built-in plugins are registered.
87+
88+
- **Type:** boolean
89+
- **Default:** `true`, unless `clearMiddleware: true` is set and `enableDefaultPlugins` is not explicitly provided
90+
- **Current built-in plugin:** `minify`
91+
92+
See [Plugins](./plugins) for details.
93+
4594
### `middleware?: Middleware[]`
4695

47-
Register one or more middleware functions to transform the HTML before parsing.
48-
Middleware lets you transform or sanitize HTML before parsing—e.g., stripping scripts, normalizing whitespace, or injecting metadata.
96+
Deprecated compatibility layer for HTML preprocessing.
4997

5098
- **Type:** [`Middleware`](./types)[]
51-
- **Default:** _[minifyMiddleware] applied automatically unless `clearMiddleware` is `true`_
99+
- **Status:** deprecated; prefer `plugins` with `beforeParse`
100+
- **Behavior:** each middleware entry is internally adapted into a plugin
52101
- **Example:**
53102

54103
```ts
@@ -62,11 +111,12 @@ Middleware lets you transform or sanitize HTML before parsing—e.g., stripping
62111

63112
### `clearMiddleware?: boolean`
64113

65-
Skips registering the default `minifyMiddleware`. When `true`, only your provided middleware functions will be used.
114+
Deprecated compatibility switch for the old middleware model.
66115

67116
- **Type:** boolean
68117
- **Default:** `false`
69-
- **Default:** `false`
118+
- **Status:** deprecated; prefer `enableDefaultPlugins: false`
119+
- **Behavior:** implies `enableDefaultPlugins: false` by default, but explicit `enableDefaultPlugins` overrides that legacy implication
70120

71121
### `styleInheritance?`
72122

@@ -269,12 +319,15 @@ Use a custom DOM parser implementation.
269319
```ts
270320
import { init } from 'html-to-document';
271321
import { MyAdapter } from './my-adapter';
272-
import { customMiddleware } from './middleware';
273322
import { CustomParser } from './parser';
274323

275324
const converter = init({
276-
clearMiddleware: false,
277-
middleware: [customMiddleware],
325+
plugins: [
326+
{
327+
beforeParse: async (html) =>
328+
html.replace(/<script[\s\S]*?>[\s\S]*?<\/script>/g, ''),
329+
},
330+
],
278331
tags: {
279332
defaultStyles: [{ key: 'p', styles: { marginBottom: 8 } }],
280333
},
@@ -293,6 +346,7 @@ converter
293346

294347
## Learn More
295348

349+
- [Plugins and hooks](./plugins)
296350
- [Building a custom adapter](./converters)
297351
- [Available tag handlers and structure](./tags)
298352
- [DocumentElement schema reference](./types)

docs/docs/api/middleware.md

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,16 @@
22
id: middleware
33
title: Middleware
44
sidebar_label: Middleware
5-
sidebar_position: 4
5+
sidebar_position: 5
66
---
77

88
# Middleware
99

10-
Middleware functions run on the HTML string _before_ it is parsed into `DocumentElement` nodes. They allow you to transform, sanitize, or minify the HTML content.
10+
`middleware` is deprecated and kept as a compatibility layer for the newer plugin system.
11+
12+
Middleware functions still run on the HTML string _before_ it is parsed into `DocumentElement` nodes, but internally each middleware entry is adapted into a plugin with a `beforeParse` hook.
13+
14+
Use [Plugins](./plugins) for new code.
1115

1216
## Signature
1317

@@ -19,17 +23,20 @@ type Middleware = (html: string) => Promise<string>;
1923

2024
See the [Types Reference](./types) for the full definition.
2125

22-
## Default Middleware
26+
## Default Middleware Behavior
27+
28+
The old built-in whitespace minifier now exists as the default `minify` plugin. `clearMiddleware: true` still disables it by default because it implies `enableDefaultPlugins: false` unless you explicitly override that.
29+
30+
The default behavior is still:
2331

24-
By default, `init()` applies a built-in whitespace-minifying middleware (unless you set `clearMiddleware: true`). This default middleware:
2532
- Strips HTML comments (`<!-- ... -->`)
2633
- Collapses consecutive whitespace into a single space (outside `<pre>`)
2734
- Removes unnecessary whitespace between tags
2835
- Trims leading and trailing whitespace
2936

3037
## Custom Middleware
3138

32-
There are two ways to register your own middleware:
39+
There are two legacy ways to register middleware:
3340

3441
### 1. Via `init` options
3542

@@ -41,7 +48,7 @@ const stripScripts: Middleware = async (html) =>
4148
html.replace(/<script[\s\S]*?>[\s\S]*?<\/script>/g, '');
4249

4350
const converter = init({
44-
clearMiddleware: true, // skip default minifier
51+
clearMiddleware: true, // skip default minifier
4552
middleware: [stripScripts],
4653
});
4754
```
@@ -56,6 +63,7 @@ converter.useMiddleware(stripScripts);
5663

5764
> **Note:** Middleware functions are executed in the order they are passed in or registered. Make sure to arrange them accordingly if one depends on the output of another.
5865
66+
When both `plugins` and deprecated `middleware` are provided through `init()` or the `Converter` constructor, plugin `beforeParse` hooks run first and adapted middleware runs after them.
5967

6068
## Example: Sanitizing HTML
6169

@@ -73,4 +81,16 @@ const converter = init({
7381
converter.convert('<p style="color:red">Hello</p>', 'docx')
7482
.then(buffer => /* ... */)
7583
.catch(console.error);
76-
```
84+
```
85+
86+
## Migration to Plugins
87+
88+
```ts
89+
const converter = init({
90+
plugins: [
91+
{
92+
beforeParse: async (html) => html.replace(/ style="[^"]*"/g, ''),
93+
},
94+
],
95+
});
96+
```

0 commit comments

Comments
 (0)