writr 4.2.0 → 4.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -22,7 +22,7 @@
22
22
  - [`.engine`](#engine)
23
23
  - [`.render(options?: RenderOptions): Promise<string>`](#renderoptions-renderoptions-promisestring)
24
24
  - [`.renderSync(options?: RenderOptions): string`](#rendersyncoptions-renderoptions-string)
25
- - [`.renderToFile(filePath: string, options?: RenderOptions): Promise<void>`](#rendertofilefilepath-string-options-renderoptions-promisevoid)
25
+ - [`.renderToFile(filePath: string, options?: RenderOptions)`](#rendertofilefilepath-string-options-renderoptions)
26
26
  - [`.renderToFileSync(filePath: string, options?: RenderOptions): void`](#rendertofilesyncfilepath-string-options-renderoptions-void)
27
27
  - [`.renderReact(options?: RenderOptions, reactOptions?: HTMLReactParserOptions): Promise<React.JSX.Element />`](#renderreactoptions-renderoptions-reactoptions-htmlreactparseroptions-promise-reactjsxelement-)
28
28
  - [`.renderReactSync( options?: RenderOptions, reactOptions?: HTMLReactParserOptions): React.JSX.Element`](#renderreactsync-options-renderoptions-reactoptions-htmlreactparseroptions-reactjsxelement)
@@ -30,6 +30,7 @@
30
30
  - [`.loadFromFileSync(filePath: string): void`](#loadfromfilesyncfilepath-string-void)
31
31
  - [`.saveToFile(filePath: string): Promise<void>`](#savetofilefilepath-string-promisevoid)
32
32
  - [`.saveToFileSync(filePath: string): void`](#savetofilesyncfilepath-string-void)
33
+ - [Hooks](#hooks)
33
34
  - [Code of Conduct and Contributing](#code-of-conduct-and-contributing)
34
35
  - [License](#license)
35
36
 
@@ -47,6 +48,7 @@
47
48
  * Github Flavor Markdown (remark-gfm).
48
49
  * Emoji Support (remark-emoji).
49
50
  * MDX Support (remark-mdx).
51
+ * Built in Hooks for adding code to render pipeline.
50
52
 
51
53
  # ESM and Node Version Support
52
54
 
@@ -252,7 +254,7 @@ const writr = new Writr(`# Hello World ::-):\n\n This is a test.`);
252
254
  const html = writr.renderSync(); // <h1>Hello World 🙂</h1><p>This is a test.</p>
253
255
  ```
254
256
 
255
- ## '.renderToFile(filePath: string, options?: RenderOptions): Promise<void>'
257
+ ## '.renderToFile(filePath: string, options?: RenderOptions)'
256
258
 
257
259
  Rendering markdown to a file. The options are based on RenderOptions.
258
260
 
@@ -347,9 +349,62 @@ writr.cache.store.lruSize = 100;
347
349
  writr.cache.store.ttl = '5m'; // setting it to 5 minutes
348
350
  ```
349
351
 
352
+ # Hooks
353
+
354
+ Hooks are a way to add additional parsing to the render pipeline. You can add hooks to the the Writr instance. Here is an example of adding a hook to the instance of Writr:
355
+
356
+ ```javascript
357
+ import { Writr, WritrHooks } from 'writr';
358
+ const writr = new Writr(`# Hello World ::-):\n\n This is a test.`);
359
+ writr.onHook(WritrHooks.beforeRender, data => {
360
+ data.body = 'Hello, Universe!';
361
+ });
362
+ const result = await writr.render();
363
+ console.log(result); // Hello, Universe!
364
+ ```
365
+
366
+ For `beforeRender` the data object is a `renderData` object. Here is the interface for `renderData`:
367
+
368
+ ```typescript
369
+ export type renderData = {
370
+ body: string
371
+ options: RenderOptions;
372
+ }
373
+ ```
374
+
375
+ For `afterRender` the data object is a `resultData` object. Here is the interface for `resultData`:
376
+
377
+ ```typescript
378
+ export type resultData = {
379
+ result: string;
380
+ }
381
+ ```
382
+
383
+ For `beforeSaveToFile` the data object is an object with the `filePath` and `content`. Here is the interface for `saveToFileData`:
384
+
385
+ ```typescript
386
+ export type saveToFileData = {
387
+ filePath: string;
388
+ content: string;
389
+ }
390
+ ```
391
+
392
+ This is called when you call `saveToFile`, `saveToFileSync`.
393
+
394
+ For `beforeRenderToFile` the data object is an object with the `filePath` and `content`. Here is the interface for `renderToFileData`:
395
+
396
+ ```typescript
397
+ export type renderToFileData = {
398
+ filePath: string;
399
+ content: string;
400
+ }
401
+ ```
402
+
403
+ This is called when you call `renderToFile`, `renderToFileSync`.
404
+
350
405
  # Code of Conduct and Contributing
351
406
  [Code of Conduct](CODE_OF_CONDUCT.md) and [Contributing](CONTRIBUTING.md) guidelines.
352
407
 
353
408
  # License
354
409
 
355
- MIT © [Jared Wray](https://jaredwray.com)
410
+ [MIT](LICENSE) & © [Jared Wray](https://jaredwray.com)
package/dist/writr.d.ts CHANGED
@@ -49,6 +49,14 @@ type RenderOptions = {
49
49
  mdx?: boolean;
50
50
  caching?: boolean;
51
51
  };
52
+ declare enum WritrHooks {
53
+ beforeRender = "beforeRender",
54
+ afterRender = "afterRender",
55
+ beforeSaveToFile = "beforeSaveToFile",
56
+ beforeRenderToFile = "beforeRenderToFile",
57
+ beforeLoadFromFile = "beforeLoadFromFile",
58
+ afterLoadFromFile = "afterLoadFromFile"
59
+ }
52
60
  declare class Writr extends Hookified {
53
61
  engine: unified.Processor<mdast.Root, mdast.Root, hast.Root, hast.Root, string>;
54
62
  private readonly _options;
@@ -182,4 +190,4 @@ declare class Writr extends Hookified {
182
190
  private mergeRenderOptions;
183
191
  }
184
192
 
185
- export { type RenderOptions, Writr, type WritrOptions };
193
+ export { type RenderOptions, Writr, WritrHooks, type WritrOptions };
package/dist/writr.js CHANGED
@@ -54,6 +54,15 @@ var WritrCache = class {
54
54
  };
55
55
 
56
56
  // src/writr.ts
57
+ var WritrHooks = /* @__PURE__ */ ((WritrHooks2) => {
58
+ WritrHooks2["beforeRender"] = "beforeRender";
59
+ WritrHooks2["afterRender"] = "afterRender";
60
+ WritrHooks2["beforeSaveToFile"] = "beforeSaveToFile";
61
+ WritrHooks2["beforeRenderToFile"] = "beforeRenderToFile";
62
+ WritrHooks2["beforeLoadFromFile"] = "beforeLoadFromFile";
63
+ WritrHooks2["afterLoadFromFile"] = "afterLoadFromFile";
64
+ return WritrHooks2;
65
+ })(WritrHooks || {});
57
66
  var Writr = class extends Hookified {
58
67
  engine = unified().use(remarkParse).use(remarkGfm).use(remarkToc).use(remarkEmoji).use(remarkRehype).use(rehypeSlug).use(remarkMath).use(rehypeKatex).use(rehypeHighlight).use(remarkMDX).use(rehypeStringify);
59
68
  // Stringify HTML
@@ -201,24 +210,33 @@ ${yamlString}---
201
210
  */
202
211
  async render(options) {
203
212
  try {
204
- let result = "";
205
- if (this.isCacheEnabled(options)) {
206
- const cached = this._cache.get(this._content, options);
207
- if (cached) {
208
- return cached;
209
- }
210
- }
211
213
  let { engine } = this;
212
214
  if (options) {
213
215
  options = { ...this._options.renderOptions, ...options };
214
216
  engine = this.createProcessor(options);
215
217
  }
216
- const file = await engine.process(this.body);
217
- result = String(file);
218
- if (this.isCacheEnabled(options)) {
219
- this._cache.set(this._content, result, options);
218
+ const renderData = {
219
+ content: this._content,
220
+ body: this.body,
221
+ options
222
+ };
223
+ await this.hook("beforeRender" /* beforeRender */, renderData);
224
+ const resultData = {
225
+ result: ""
226
+ };
227
+ if (this.isCacheEnabled(renderData.options)) {
228
+ const cached = this._cache.get(renderData.content, renderData.options);
229
+ if (cached) {
230
+ return cached;
231
+ }
220
232
  }
221
- return result;
233
+ const file = await engine.process(renderData.body);
234
+ resultData.result = String(file);
235
+ if (this.isCacheEnabled(renderData.options)) {
236
+ this._cache.set(renderData.content, resultData.result, renderData.options);
237
+ }
238
+ await this.hook("afterRender" /* afterRender */, resultData);
239
+ return resultData.result;
222
240
  } catch (error) {
223
241
  throw new Error(`Failed to render markdown: ${error.message}`);
224
242
  }
@@ -230,24 +248,33 @@ ${yamlString}---
230
248
  */
231
249
  renderSync(options) {
232
250
  try {
233
- let result = "";
234
- if (this.isCacheEnabled(options)) {
235
- const cached = this._cache.get(this._content, options);
236
- if (cached) {
237
- return cached;
238
- }
239
- }
240
251
  let { engine } = this;
241
252
  if (options) {
242
253
  options = { ...this._options.renderOptions, ...options };
243
254
  engine = this.createProcessor(options);
244
255
  }
245
- const file = engine.processSync(this.body);
246
- result = String(file);
247
- if (this.isCacheEnabled(options)) {
248
- this._cache.set(this._content, result, options);
256
+ const renderData = {
257
+ content: this._content,
258
+ body: this.body,
259
+ options
260
+ };
261
+ this.hook("beforeRender" /* beforeRender */, renderData);
262
+ const resultData = {
263
+ result: ""
264
+ };
265
+ if (this.isCacheEnabled(renderData.options)) {
266
+ const cached = this._cache.get(renderData.content, renderData.options);
267
+ if (cached) {
268
+ return cached;
269
+ }
249
270
  }
250
- return result;
271
+ const file = engine.processSync(renderData.body);
272
+ resultData.result = String(file);
273
+ if (this.isCacheEnabled(renderData.options)) {
274
+ this._cache.set(renderData.content, resultData.result, renderData.options);
275
+ }
276
+ this.hook("afterRender" /* afterRender */, resultData);
277
+ return resultData.result;
251
278
  } catch (error) {
252
279
  throw new Error(`Failed to render markdown: ${error.message}`);
253
280
  }
@@ -263,7 +290,12 @@ ${yamlString}---
263
290
  const directoryPath = dirname(filePath);
264
291
  const content = await this.render(options);
265
292
  await mkdir(directoryPath, { recursive: true });
266
- await writeFile(filePath, content, "utf8");
293
+ const data = {
294
+ filePath,
295
+ content
296
+ };
297
+ await this.hook("beforeRenderToFile" /* beforeRenderToFile */, data);
298
+ await writeFile(data.filePath, data.content);
267
299
  } catch (error) {
268
300
  this.emit("error", error);
269
301
  if (this._options.throwErrors) {
@@ -281,7 +313,12 @@ ${yamlString}---
281
313
  const directoryPath = dirname(filePath);
282
314
  const content = this.renderSync(options);
283
315
  fs.mkdirSync(directoryPath, { recursive: true });
284
- fs.writeFileSync(filePath, content, "utf8");
316
+ const data = {
317
+ filePath,
318
+ content
319
+ };
320
+ this.hook("beforeRenderToFile" /* beforeRenderToFile */, data);
321
+ fs.writeFileSync(data.filePath, data.content);
285
322
  } catch (error) {
286
323
  this.emit("error", error);
287
324
  if (this._options.throwErrors) {
@@ -336,7 +373,12 @@ ${yamlString}---
336
373
  const { writeFile, mkdir } = fs.promises;
337
374
  const directoryPath = dirname(filePath);
338
375
  await mkdir(directoryPath, { recursive: true });
339
- await writeFile(filePath, this._content, "utf8");
376
+ const data = {
377
+ filePath,
378
+ content: this._content
379
+ };
380
+ await this.hook("beforeSaveToFile" /* beforeSaveToFile */, data);
381
+ await writeFile(data.filePath, data.content);
340
382
  } catch (error) {
341
383
  this.emit("error", error);
342
384
  if (this._options.throwErrors) {
@@ -353,7 +395,12 @@ ${yamlString}---
353
395
  try {
354
396
  const directoryPath = dirname(filePath);
355
397
  fs.mkdirSync(directoryPath, { recursive: true });
356
- fs.writeFileSync(filePath, this._content, "utf8");
398
+ const data = {
399
+ filePath,
400
+ content: this._content
401
+ };
402
+ this.hook("beforeSaveToFile" /* beforeSaveToFile */, data);
403
+ fs.writeFileSync(data.filePath, data.content);
357
404
  } catch (error) {
358
405
  this.emit("error", error);
359
406
  if (this._options.throwErrors) {
@@ -433,5 +480,6 @@ ${yamlString}---
433
480
  }
434
481
  };
435
482
  export {
436
- Writr
483
+ Writr,
484
+ WritrHooks
437
485
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "writr",
3
- "version": "4.2.0",
3
+ "version": "4.4.0",
4
4
  "description": "Markdown Rendering Simplified",
5
5
  "type": "module",
6
6
  "main": "./dist/writr.js",
@@ -53,11 +53,11 @@
53
53
  "website:serve": "rimraf ./site/README.md ./site/dist && npx docula serve -s ./site -o ./site/dist"
54
54
  },
55
55
  "dependencies": {
56
- "cacheable": "^1.8.5",
57
- "hookified": "^1.5.1",
58
- "html-react-parser": "^5.1.18",
56
+ "cacheable": "^1.8.8",
57
+ "hookified": "^1.7.0",
58
+ "html-react-parser": "^5.2.2",
59
59
  "js-yaml": "^4.1.0",
60
- "react": "^18.3.1",
60
+ "react": "^19.0.0",
61
61
  "rehype-highlight": "^7.0.1",
62
62
  "rehype-katex": "^7.0.1",
63
63
  "rehype-slug": "^6.0.0",
@@ -73,17 +73,17 @@
73
73
  },
74
74
  "devDependencies": {
75
75
  "@types/js-yaml": "^4.0.9",
76
- "@types/node": "^22.10.1",
77
- "@types/react": "^18.3.12",
78
- "@vitest/coverage-v8": "^2.1.6",
79
- "docula": "^0.9.5",
76
+ "@types/node": "^22.12.0",
77
+ "@types/react": "^19.0.8",
78
+ "@vitest/coverage-v8": "^3.0.4",
79
+ "docula": "^0.10.0",
80
80
  "rimraf": "^6.0.1",
81
81
  "ts-node": "^10.9.2",
82
- "tsup": "^8.3.5",
83
- "typescript": "^5.7.2",
84
- "vitest": "^2.1.6",
85
- "webpack": "^5.96.1",
86
- "xo": "^0.59.3"
82
+ "tsup": "^8.3.6",
83
+ "typescript": "^5.7.3",
84
+ "vitest": "^3.0.4",
85
+ "webpack": "^5.97.1",
86
+ "xo": "^0.60.0"
87
87
  },
88
88
  "xo": {
89
89
  "ignores": [