diff --git a/.gitignore b/.gitignore
index 6dedd8e232..dc62331aa8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,3 +9,4 @@ docs/LICENSE.md
vuln.js
man/marked.1
marked.min.js
+test.js
diff --git a/docs/USING_PRO.md b/docs/USING_PRO.md
index b2c20718d6..6c7c2922c3 100644
--- a/docs/USING_PRO.md
+++ b/docs/USING_PRO.md
@@ -261,6 +261,8 @@ Hooks are methods that hook into some part of marked. The following hooks are av
| `preprocess(markdown: string): string` | Process markdown before sending it to marked. |
| `postprocess(html: string): string` | Process html after marked has finished parsing. |
| `processAllTokens(tokens: Token[]): Token[]` | Process all tokens before walk tokens. |
+| `provideLexer(): (src: string, options?: MarkedOptions) => Token[]` | Provide function to tokenize markdown. |
+| `provideParser(): (tokens: Token[], options?: MarkedOptions) => string` | Provide function to parse tokens. |
`marked.use()` can be called multiple times with different `hooks` functions. Each function will be called in order, starting with the function that was assigned *last*.
@@ -325,6 +327,45 @@ console.log(marked.parse(`
```
+**Example:** Save reflinks for chunked rendering
+
+```js
+import { marked, Lexer } from 'marked';
+
+let refLinks = {};
+
+// Override function
+function processAllTokens(tokens) {
+ refLinks = tokens.links;
+ return tokens;
+}
+
+function provideLexer(src, options) {
+ return (src, options) => {
+ const lexer = new Lexer(options);
+ lexer.tokens.links = refLinks;
+ return this.block ? lexer.lex(src) : lexer.inlineTokens(src);
+ };
+}
+
+marked.use({ hooks: { processAllTokens, provideLexer } });
+
+// Parse reflinks separately from markdown that uses them
+marked.parse(`
+[test]: http://example.com
+`);
+
+console.log(marked.parse(`
+[test link][test]
+`));
+```
+
+**Output:**
+
+```html
+
test link
+```
+
***
Custom Extensions : extensions
diff --git a/src/Hooks.ts b/src/Hooks.ts
index 967a05e8f0..8d84b89c0d 100644
--- a/src/Hooks.ts
+++ b/src/Hooks.ts
@@ -1,9 +1,12 @@
import { _defaults } from './defaults.ts';
+import { _Lexer } from './Lexer.ts';
+import { _Parser } from './Parser.ts';
import type { MarkedOptions } from './MarkedOptions.ts';
import type { Token, TokensList } from './Tokens.ts';
export class _Hooks {
options: MarkedOptions;
+ block: boolean | undefined;
constructor(options?: MarkedOptions) {
this.options = options || _defaults;
@@ -35,4 +38,18 @@ export class _Hooks {
processAllTokens(tokens: Token[] | TokensList) {
return tokens;
}
+
+ /**
+ * Provide function to tokenize markdown
+ */
+ provideLexer() {
+ return this.block ? _Lexer.lex : _Lexer.lexInline;
+ }
+
+ /**
+ * Provide function to parse tokens
+ */
+ provideParser() {
+ return this.block ? _Parser.parse : _Parser.parseInline;
+ }
}
diff --git a/src/Instance.ts b/src/Instance.ts
index 758a21d7d9..abadb87230 100644
--- a/src/Instance.ts
+++ b/src/Instance.ts
@@ -18,8 +18,8 @@ export class Marked {
defaults = _getDefaults();
options = this.setOptions;
- parse = this.parseMarkdown(_Lexer.lex, _Parser.parse);
- parseInline = this.parseMarkdown(_Lexer.lexInline, _Parser.parseInline);
+ parse = this.parseMarkdown(true);
+ parseInline = this.parseMarkdown(false);
Parser = _Parser;
Renderer = _Renderer;
@@ -195,11 +195,11 @@ export class Marked {
if (!(prop in hooks)) {
throw new Error(`hook '${prop}' does not exist`);
}
- if (prop === 'options') {
- // ignore options property
+ if (['options', 'block'].includes(prop)) {
+ // ignore options and block properties
continue;
}
- const hooksProp = prop as Exclude;
+ const hooksProp = prop as Exclude;
const hooksFunc = pack.hooks[hooksProp] as UnknownFunction;
const prevHook = hooks[hooksProp] as UnknownFunction;
if (_Hooks.passThroughHooks.has(prop)) {
@@ -261,7 +261,7 @@ export class Marked {
return _Parser.parse(tokens, options ?? this.defaults);
}
- private parseMarkdown(lexer: (src: string, options?: MarkedOptions) => TokensList | Token[], parser: (tokens: Token[], options?: MarkedOptions) => string) {
+ private parseMarkdown(blockType: boolean) {
type overloadedParse = {
(src: string, options: MarkedOptions & { async: true }): Promise;
(src: string, options: MarkedOptions & { async: false }): string;
@@ -291,8 +291,12 @@ export class Marked {
if (opt.hooks) {
opt.hooks.options = opt;
+ opt.hooks.block = blockType;
}
+ const lexer = opt.hooks ? opt.hooks.provideLexer() : (blockType ? _Lexer.lex : _Lexer.lexInline);
+ const parser = opt.hooks ? opt.hooks.provideParser() : (blockType ? _Parser.parse : _Parser.parseInline);
+
if (opt.async) {
return Promise.resolve(opt.hooks ? opt.hooks.preprocess(src) : src)
.then(src => lexer(src, opt))
@@ -309,7 +313,7 @@ export class Marked {
}
let tokens = lexer(src, opt);
if (opt.hooks) {
- tokens = opt.hooks.processAllTokens(tokens) as Token[] | TokensList;
+ tokens = opt.hooks.processAllTokens(tokens);
}
if (opt.walkTokens) {
this.walkTokens(tokens, opt.walkTokens);
diff --git a/src/MarkedOptions.ts b/src/MarkedOptions.ts
index 9c4c1fe7cb..a1bf485d7a 100644
--- a/src/MarkedOptions.ts
+++ b/src/MarkedOptions.ts
@@ -34,7 +34,7 @@ export interface RendererExtension {
export type TokenizerAndRendererExtension = TokenizerExtension | RendererExtension | (TokenizerExtension & RendererExtension);
-type HooksApi = Omit<_Hooks, 'constructor' | 'options'>;
+type HooksApi = Omit<_Hooks, 'constructor' | 'options' | 'block'>;
type HooksObject = {
[K in keyof HooksApi]?: (this: _Hooks, ...args: Parameters) => ReturnType | Promise>
};
@@ -77,6 +77,8 @@ export interface MarkedExtension {
* preprocess is called to process markdown before sending it to marked.
* processAllTokens is called with the TokensList before walkTokens.
* postprocess is called to process html after marked has finished parsing.
+ * provideLexer is called to provide a function to tokenize markdown.
+ * provideParser is called to provide a function to parse tokens.
*/
hooks?: HooksObject | undefined | null;
diff --git a/test/types/marked.ts b/test/types/marked.ts
index bf7a3e9433..b8caa9ab50 100644
--- a/test/types/marked.ts
+++ b/test/types/marked.ts
@@ -346,6 +346,16 @@ marked.use({
}
}
});
+marked.use({
+ hooks: {
+ provideLexer() {
+ return this.block ? Lexer.lex : Lexer.lexInline;
+ },
+ provideParser() {
+ return this.block ? Parser.parse : Parser.parseInline;
+ },
+ }
+});
marked.use({
async: true,
hooks: {
diff --git a/test/unit/Hooks.test.js b/test/unit/Hooks.test.js
index 8907633069..83f7676115 100644
--- a/test/unit/Hooks.test.js
+++ b/test/unit/Hooks.test.js
@@ -190,4 +190,72 @@ describe('Hooks', () => {
postprocess2 async
postprocess1
`);
});
+
+ it('should provide lexer', () => {
+ marked.use({
+ hooks: {
+ provideLexer() {
+ return (src) => [createHeadingToken(src)];
+ },
+ },
+ });
+ const html = marked.parse('text');
+ assert.strictEqual(html.trim(), 'text
');
+ });
+
+ it('should provide lexer async', async() => {
+ marked.use({
+ async: true,
+ hooks: {
+ provideLexer() {
+ return async(src) => {
+ await timeout();
+ return [createHeadingToken(src)];
+ };
+ },
+ },
+ });
+ const html = await marked.parse('text');
+ assert.strictEqual(html.trim(), 'text
');
+ });
+
+ it('should provide parser return object', () => {
+ marked.use({
+ hooks: {
+ provideParser() {
+ return (tokens) => ({ text: 'test parser' });
+ },
+ },
+ });
+ const html = marked.parse('text');
+ assert.strictEqual(html.text, 'test parser');
+ });
+
+ it('should provide parser', () => {
+ marked.use({
+ hooks: {
+ provideParser() {
+ return (tokens) => 'test parser';
+ },
+ },
+ });
+ const html = marked.parse('text');
+ assert.strictEqual(html.trim(), 'test parser');
+ });
+
+ it('should provide parser async', async() => {
+ marked.use({
+ async: true,
+ hooks: {
+ provideParser() {
+ return async(tokens) => {
+ await timeout();
+ return 'test parser';
+ };
+ },
+ },
+ });
+ const html = await marked.parse('text');
+ assert.strictEqual(html.trim(), 'test parser');
+ });
});