|
| 1 | +# `html-rewriter-wasm` |
| 2 | + |
| 3 | +An implementation of |
| 4 | +[HTMLRewriter](https://developers.cloudflare.com/workers/runtime-apis/html-rewriter) |
| 5 | +using a WebAssembly version of |
| 6 | +[lol-html](https://github.com/cloudflare/lol-html/). This was primarily written |
| 7 | +for [🔥 Miniflare](https://github.com/mrbbot/miniflare), but may be useful for |
| 8 | +other projects too. Many thanks to [@inikulin](https://github.com/inikulin) for |
| 9 | +their work on |
| 10 | +[lol-html's JavaScript API](https://github.com/cloudflare/lol-html/tree/master/js-api) |
| 11 | +which this package's Rust code is based on. |
| 12 | + |
| 13 | +## Usage |
| 14 | + |
| 15 | +```js |
| 16 | +import { HTMLRewriter } from "html-rewriter-wasm"; |
| 17 | + |
| 18 | +const encoder = new TextEncoder(); |
| 19 | +const decoder = new TextDecoder(); |
| 20 | + |
| 21 | +const rewriter = new HTMLRewriter((outputChunk) => { |
| 22 | + if (outputChunk.length === 0) { |
| 23 | + // Remember to free memory on last chunk |
| 24 | + queueMicrotask(() => rewriter.free()); |
| 25 | + } else { |
| 26 | + console.log(decoder.decode(outputChunk)); // <p>new</p> |
| 27 | + } |
| 28 | +}); |
| 29 | + |
| 30 | +rewriter.on("p", { |
| 31 | + element(element) { |
| 32 | + element.setInnerContent("new"); |
| 33 | + }, |
| 34 | +}); |
| 35 | + |
| 36 | +await rewriter.write(encoder.encode("<p>old</p>")); |
| 37 | +await rewriter.end(); |
| 38 | +``` |
| 39 | + |
| 40 | +See [test/index.ts](./test/index.ts) for a more traditional `HTMLRewriter` |
| 41 | +implementation that doesn't have the caveats listed below, but restricts input |
| 42 | +and output to strings. |
| 43 | + |
| 44 | +## Caveats |
| 45 | + |
| 46 | +- `end` may only be called once per `HTMLRewriter` instance. This means you must |
| 47 | + create a new `HTMLRewriter` instance for each transformation: |
| 48 | + |
| 49 | + ```js |
| 50 | + // ❌ |
| 51 | + const rewriter = new HTMLRewriter(...); |
| 52 | + await rewriter.end(); |
| 53 | + await rewriter.end(); // not allowed |
| 54 | + |
| 55 | + // ✅ |
| 56 | + const rewriter1 = new HTMLRewriter(...); |
| 57 | + await rewriter1.end(); |
| 58 | + const rewriter2 = new HTMLRewriter(...); |
| 59 | + await rewriter2.end(); |
| 60 | + ``` |
| 61 | + |
| 62 | +- When using `async` handlers, you must always `await` calls to `write` and |
| 63 | + `end` before calling them again. In other words, you cannot have concurrent |
| 64 | + `write` and `end` calls: |
| 65 | + |
| 66 | + ```js |
| 67 | + const rewriter = new HTMLRewriter(...).on("p", { |
| 68 | + async element(element) { |
| 69 | + await fetch(...); |
| 70 | + element.setInnerContent("new"); |
| 71 | + } |
| 72 | + }); |
| 73 | + |
| 74 | + // ❌ |
| 75 | + rewriter.write(encoder.encode("<p>1</p>")); |
| 76 | + rewriter.write(encoder.encode("<p>2</p>")); // not allowed |
| 77 | + |
| 78 | + // ❌ |
| 79 | + const promise1 = rewriter.write(encoder.encode("<p>1</p>")); |
| 80 | + const promise2 = rewriter.write(encoder.encode("<p>2</p>")); |
| 81 | + await Promise.all([promise1, promise2]); // not allowed |
| 82 | + |
| 83 | + // ✅ |
| 84 | + await rewriter.write(encoder.encode("<p>1</p>")); |
| 85 | + await rewriter.write(encoder.encode("<p>2</p>")); |
| 86 | + ``` |
| 87 | + |
| 88 | +- If using handler classes, you must bind their methods to the class first: |
| 89 | + |
| 90 | + ```js |
| 91 | + class Handler { |
| 92 | + constructor(value) { |
| 93 | + this.value = value; |
| 94 | + } |
| 95 | + |
| 96 | + element(element) { |
| 97 | + element.setInnerContent(this.value); |
| 98 | + } |
| 99 | + } |
| 100 | + const rewriter = new HTMLRewriter(...); |
| 101 | + const handler = new Handler("new"); |
| 102 | + |
| 103 | + // ❌ |
| 104 | + rewriter.on("p", handler); |
| 105 | + |
| 106 | + // ✅ |
| 107 | + rewriter.on("p", { |
| 108 | + element: handler.element.bind(handler) |
| 109 | + }) |
| 110 | + ``` |
| 111 | + |
| 112 | +## Building |
| 113 | + |
| 114 | +You can build the package by running `npm run build`. You must do this prior to |
| 115 | +running tests with `npm test`. You **must** have mrbbot's fork of wasm-pack |
| 116 | +installed. This upgrades binaryen (wasm-opt) to version_92 which exports |
| 117 | +`asyncify_get_state`. |
| 118 | + |
| 119 | +## License |
| 120 | + |
| 121 | +`html-rewriter-wasm` uses `lol-html` which is BSD 3-Clause licensed: |
| 122 | + |
| 123 | +``` |
| 124 | +Copyright (C) 2019, Cloudflare, Inc. |
| 125 | +All rights reserved. |
| 126 | +
|
| 127 | +Redistribution and use in source and binary forms, with or without modification, |
| 128 | +are permitted provided that the following conditions are met: |
| 129 | +
|
| 130 | +1. Redistributions of source code must retain the above copyright notice, this |
| 131 | +list of conditions and the following disclaimer. |
| 132 | +
|
| 133 | +2. Redistributions in binary form must reproduce the above copyright notice, |
| 134 | +this list of conditions and the following disclaimer in the documentation and/or |
| 135 | +other materials provided with the distribution. |
| 136 | +
|
| 137 | +3. Neither the name of the copyright holder nor the names of its contributors |
| 138 | +may be used to endorse or promote products derived from this software without |
| 139 | +specific prior written permission. |
| 140 | +
|
| 141 | +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND |
| 142 | +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
| 143 | +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| 144 | +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR |
| 145 | +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
| 146 | +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
| 147 | +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON |
| 148 | +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| 149 | +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
| 150 | +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 151 | +``` |
0 commit comments