feat: add async option (#2474)
* fix: return values from walkTokens * docs: add async docs * test: add async test * docs: add nav to async * Update docs/USING_PRO.md Co-authored-by: Steven <steven@ceriously.com> * test: expect promise * Update docs/USING_ADVANCED.md Co-authored-by: Steven <steven@ceriously.com> Co-authored-by: Steven <steven@ceriously.com>
This commit is contained in:
parent
33724a3201
commit
994b2e6127
@ -44,6 +44,7 @@ console.log(marked.parse(markdownString));
|
|||||||
|
|
||||||
|Member |Type |Default |Since |Notes |
|
|Member |Type |Default |Since |Notes |
|
||||||
|:-----------|:---------|:--------|:--------|:-------------|
|
|:-----------|:---------|:--------|:--------|:-------------|
|
||||||
|
|async |`boolean` |`false` |4.1.0 |If true, `walkTokens` functions can be async and `marked.parse` will return a promise that resolves when all walk tokens functions resolve.|
|
||||||
|baseUrl |`string` |`null` |0.3.9 |A prefix url for any relative link. |
|
|baseUrl |`string` |`null` |0.3.9 |A prefix url for any relative link. |
|
||||||
|breaks |`boolean` |`false` |v0.2.7 |If true, add `<br>` on a single line break (copies GitHub behavior on comments, but not on rendered markdown files). Requires `gfm` be `true`.|
|
|breaks |`boolean` |`false` |v0.2.7 |If true, add `<br>` on a single line break (copies GitHub behavior on comments, but not on rendered markdown files). Requires `gfm` be `true`.|
|
||||||
|gfm |`boolean` |`true` |v0.2.1 |If true, use approved [GitHub Flavored Markdown (GFM) specification](https://github.github.com/gfm/).|
|
|gfm |`boolean` |`true` |v0.2.1 |If true, use approved [GitHub Flavored Markdown (GFM) specification](https://github.github.com/gfm/).|
|
||||||
|
@ -438,6 +438,78 @@ console.log(marked.parse('A Description List:\n'
|
|||||||
|
|
||||||
***
|
***
|
||||||
|
|
||||||
|
<h2 id="async">Async Marked : <code>async</code></h2>
|
||||||
|
|
||||||
|
Marked will return a promise if the `async` option is true. The `async` option will tell marked to await any `walkTokens` functions before parsing the tokens and returning an HTML string.
|
||||||
|
|
||||||
|
Simple Example:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const walkTokens = async (token) => {
|
||||||
|
if (token.type === 'link') {
|
||||||
|
try {
|
||||||
|
await fetch(token.href);
|
||||||
|
} catch (ex) {
|
||||||
|
token.title = 'invalid';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
marked.use({ walkTokens, async: true });
|
||||||
|
|
||||||
|
const markdown = `
|
||||||
|
[valid link](https://example.com)
|
||||||
|
|
||||||
|
[invalid link](https://invalidurl.com)
|
||||||
|
`;
|
||||||
|
|
||||||
|
const html = await marked.parse(markdown);
|
||||||
|
```
|
||||||
|
|
||||||
|
Custom Extension Example:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const importUrl = {
|
||||||
|
extensions: [{
|
||||||
|
name: 'importUrl',
|
||||||
|
level: 'block',
|
||||||
|
start(src) { return src.indexOf('\n:'); },
|
||||||
|
tokenizer(src) {
|
||||||
|
const rule = /^:(https?:\/\/.+?):/;
|
||||||
|
const match = rule.exec(src);
|
||||||
|
if (match) {
|
||||||
|
return {
|
||||||
|
type: 'importUrl',
|
||||||
|
raw: match[0],
|
||||||
|
url: match[1],
|
||||||
|
html: '' // will be replaced in walkTokens
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
renderer(token) {
|
||||||
|
return token.html;
|
||||||
|
}
|
||||||
|
}],
|
||||||
|
async: true, // needed to tell marked to return a promise
|
||||||
|
async walkTokens(token) {
|
||||||
|
if (token.type === 'importUrl') {
|
||||||
|
const res = await fetch(token.url);
|
||||||
|
token.html = await res.text();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
marked.use(importUrl);
|
||||||
|
|
||||||
|
const markdown = `
|
||||||
|
# example.com
|
||||||
|
|
||||||
|
:https://example.com:
|
||||||
|
`;
|
||||||
|
|
||||||
|
const html = await marked.parse(markdown);
|
||||||
|
```
|
||||||
|
|
||||||
<h2 id="lexer">The Lexer</h2>
|
<h2 id="lexer">The Lexer</h2>
|
||||||
|
|
||||||
The lexer takes a markdown string and calls the tokenizer functions.
|
The lexer takes a markdown string and calls the tokenizer functions.
|
||||||
|
@ -51,6 +51,7 @@
|
|||||||
<li><a href="/using_pro#tokenizer">Tokenizer</a></li>
|
<li><a href="/using_pro#tokenizer">Tokenizer</a></li>
|
||||||
<li><a href="/using_pro#walk-tokens">Walk Tokens</a></li>
|
<li><a href="/using_pro#walk-tokens">Walk Tokens</a></li>
|
||||||
<li><a href="/using_pro#extensions">Custom Extensions</a></li>
|
<li><a href="/using_pro#extensions">Custom Extensions</a></li>
|
||||||
|
<li><a href="/using_pro#async">Async Marked</a></li>
|
||||||
<li><a href="/using_pro#lexer">Lexer</a></li>
|
<li><a href="/using_pro#lexer">Lexer</a></li>
|
||||||
<li><a href="/using_pro#parser">Parser</a></li>
|
<li><a href="/using_pro#parser">Parser</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
@ -19,7 +19,7 @@ function outputLink(cap, link, raw, lexer) {
|
|||||||
href,
|
href,
|
||||||
title,
|
title,
|
||||||
text,
|
text,
|
||||||
tokens: lexer.inlineTokens(text, [])
|
tokens: lexer.inlineTokens(text)
|
||||||
};
|
};
|
||||||
lexer.state.inLink = false;
|
lexer.state.inLink = false;
|
||||||
return token;
|
return token;
|
||||||
@ -125,15 +125,13 @@ export class Tokenizer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const token = {
|
return {
|
||||||
type: 'heading',
|
type: 'heading',
|
||||||
raw: cap[0],
|
raw: cap[0],
|
||||||
depth: cap[1].length,
|
depth: cap[1].length,
|
||||||
text,
|
text,
|
||||||
tokens: []
|
tokens: this.lexer.inline(text)
|
||||||
};
|
};
|
||||||
this.lexer.inline(token.text, token.tokens);
|
|
||||||
return token;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -355,10 +353,10 @@ export class Tokenizer {
|
|||||||
text: cap[0]
|
text: cap[0]
|
||||||
};
|
};
|
||||||
if (this.options.sanitize) {
|
if (this.options.sanitize) {
|
||||||
|
const text = this.options.sanitizer ? this.options.sanitizer(cap[0]) : escape(cap[0]);
|
||||||
token.type = 'paragraph';
|
token.type = 'paragraph';
|
||||||
token.text = this.options.sanitizer ? this.options.sanitizer(cap[0]) : escape(cap[0]);
|
token.text = text;
|
||||||
token.tokens = [];
|
token.tokens = this.lexer.inline(text);
|
||||||
this.lexer.inline(token.text, token.tokens);
|
|
||||||
}
|
}
|
||||||
return token;
|
return token;
|
||||||
}
|
}
|
||||||
@ -416,8 +414,7 @@ export class Tokenizer {
|
|||||||
// header child tokens
|
// header child tokens
|
||||||
l = item.header.length;
|
l = item.header.length;
|
||||||
for (j = 0; j < l; j++) {
|
for (j = 0; j < l; j++) {
|
||||||
item.header[j].tokens = [];
|
item.header[j].tokens = this.lexer.inline(item.header[j].text);
|
||||||
this.lexer.inline(item.header[j].text, item.header[j].tokens);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// cell child tokens
|
// cell child tokens
|
||||||
@ -425,8 +422,7 @@ export class Tokenizer {
|
|||||||
for (j = 0; j < l; j++) {
|
for (j = 0; j < l; j++) {
|
||||||
row = item.rows[j];
|
row = item.rows[j];
|
||||||
for (k = 0; k < row.length; k++) {
|
for (k = 0; k < row.length; k++) {
|
||||||
row[k].tokens = [];
|
row[k].tokens = this.lexer.inline(row[k].text);
|
||||||
this.lexer.inline(row[k].text, row[k].tokens);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -438,45 +434,40 @@ export class Tokenizer {
|
|||||||
lheading(src) {
|
lheading(src) {
|
||||||
const cap = this.rules.block.lheading.exec(src);
|
const cap = this.rules.block.lheading.exec(src);
|
||||||
if (cap) {
|
if (cap) {
|
||||||
const token = {
|
return {
|
||||||
type: 'heading',
|
type: 'heading',
|
||||||
raw: cap[0],
|
raw: cap[0],
|
||||||
depth: cap[2].charAt(0) === '=' ? 1 : 2,
|
depth: cap[2].charAt(0) === '=' ? 1 : 2,
|
||||||
text: cap[1],
|
text: cap[1],
|
||||||
tokens: []
|
tokens: this.lexer.inline(cap[1])
|
||||||
};
|
};
|
||||||
this.lexer.inline(token.text, token.tokens);
|
|
||||||
return token;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
paragraph(src) {
|
paragraph(src) {
|
||||||
const cap = this.rules.block.paragraph.exec(src);
|
const cap = this.rules.block.paragraph.exec(src);
|
||||||
if (cap) {
|
if (cap) {
|
||||||
const token = {
|
const text = cap[1].charAt(cap[1].length - 1) === '\n'
|
||||||
|
? cap[1].slice(0, -1)
|
||||||
|
: cap[1];
|
||||||
|
return {
|
||||||
type: 'paragraph',
|
type: 'paragraph',
|
||||||
raw: cap[0],
|
raw: cap[0],
|
||||||
text: cap[1].charAt(cap[1].length - 1) === '\n'
|
text,
|
||||||
? cap[1].slice(0, -1)
|
tokens: this.lexer.inline(text)
|
||||||
: cap[1],
|
|
||||||
tokens: []
|
|
||||||
};
|
};
|
||||||
this.lexer.inline(token.text, token.tokens);
|
|
||||||
return token;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
text(src) {
|
text(src) {
|
||||||
const cap = this.rules.block.text.exec(src);
|
const cap = this.rules.block.text.exec(src);
|
||||||
if (cap) {
|
if (cap) {
|
||||||
const token = {
|
return {
|
||||||
type: 'text',
|
type: 'text',
|
||||||
raw: cap[0],
|
raw: cap[0],
|
||||||
text: cap[0],
|
text: cap[0],
|
||||||
tokens: []
|
tokens: this.lexer.inline(cap[0])
|
||||||
};
|
};
|
||||||
this.lexer.inline(token.text, token.tokens);
|
|
||||||
return token;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -645,7 +636,7 @@ export class Tokenizer {
|
|||||||
type: 'em',
|
type: 'em',
|
||||||
raw: src.slice(0, lLength + match.index + rLength + 1),
|
raw: src.slice(0, lLength + match.index + rLength + 1),
|
||||||
text,
|
text,
|
||||||
tokens: this.lexer.inlineTokens(text, [])
|
tokens: this.lexer.inlineTokens(text)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -655,7 +646,7 @@ export class Tokenizer {
|
|||||||
type: 'strong',
|
type: 'strong',
|
||||||
raw: src.slice(0, lLength + match.index + rLength + 1),
|
raw: src.slice(0, lLength + match.index + rLength + 1),
|
||||||
text,
|
text,
|
||||||
tokens: this.lexer.inlineTokens(text, [])
|
tokens: this.lexer.inlineTokens(text)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -696,7 +687,7 @@ export class Tokenizer {
|
|||||||
type: 'del',
|
type: 'del',
|
||||||
raw: cap[0],
|
raw: cap[0],
|
||||||
text: cap[2],
|
text: cap[2],
|
||||||
tokens: this.lexer.inlineTokens(cap[2], [])
|
tokens: this.lexer.inlineTokens(cap[2])
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
export function getDefaults() {
|
export function getDefaults() {
|
||||||
return {
|
return {
|
||||||
|
async: false,
|
||||||
baseUrl: null,
|
baseUrl: null,
|
||||||
breaks: false,
|
breaks: false,
|
||||||
extensions: null,
|
extensions: null,
|
||||||
|
@ -105,13 +105,7 @@ export function marked(src, opt, callback) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
function onError(e) {
|
||||||
const tokens = Lexer.lex(src, opt);
|
|
||||||
if (opt.walkTokens) {
|
|
||||||
marked.walkTokens(tokens, opt.walkTokens);
|
|
||||||
}
|
|
||||||
return Parser.parse(tokens, opt);
|
|
||||||
} catch (e) {
|
|
||||||
e.message += '\nPlease report this to https://github.com/markedjs/marked.';
|
e.message += '\nPlease report this to https://github.com/markedjs/marked.';
|
||||||
if (opt.silent) {
|
if (opt.silent) {
|
||||||
return '<p>An error occurred:</p><pre>'
|
return '<p>An error occurred:</p><pre>'
|
||||||
@ -120,6 +114,23 @@ export function marked(src, opt, callback) {
|
|||||||
}
|
}
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const tokens = Lexer.lex(src, opt);
|
||||||
|
if (opt.walkTokens) {
|
||||||
|
if (opt.async) {
|
||||||
|
return Promise.all(marked.walkTokens(tokens, opt.walkTokens))
|
||||||
|
.then(() => {
|
||||||
|
return Parser.parse(tokens, opt);
|
||||||
|
})
|
||||||
|
.catch(onError);
|
||||||
|
}
|
||||||
|
marked.walkTokens(tokens, opt.walkTokens);
|
||||||
|
}
|
||||||
|
return Parser.parse(tokens, opt);
|
||||||
|
} catch (e) {
|
||||||
|
onError(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -236,10 +247,12 @@ marked.use = function(...args) {
|
|||||||
if (pack.walkTokens) {
|
if (pack.walkTokens) {
|
||||||
const walkTokens = marked.defaults.walkTokens;
|
const walkTokens = marked.defaults.walkTokens;
|
||||||
opts.walkTokens = function(token) {
|
opts.walkTokens = function(token) {
|
||||||
pack.walkTokens.call(this, token);
|
let values = [];
|
||||||
|
values.push(pack.walkTokens.call(this, token));
|
||||||
if (walkTokens) {
|
if (walkTokens) {
|
||||||
walkTokens.call(this, token);
|
values = values.concat(walkTokens.call(this, token));
|
||||||
}
|
}
|
||||||
|
return values;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -256,35 +269,37 @@ marked.use = function(...args) {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
marked.walkTokens = function(tokens, callback) {
|
marked.walkTokens = function(tokens, callback) {
|
||||||
|
let values = [];
|
||||||
for (const token of tokens) {
|
for (const token of tokens) {
|
||||||
callback.call(marked, token);
|
values = values.concat(callback.call(marked, token));
|
||||||
switch (token.type) {
|
switch (token.type) {
|
||||||
case 'table': {
|
case 'table': {
|
||||||
for (const cell of token.header) {
|
for (const cell of token.header) {
|
||||||
marked.walkTokens(cell.tokens, callback);
|
values = values.concat(marked.walkTokens(cell.tokens, callback));
|
||||||
}
|
}
|
||||||
for (const row of token.rows) {
|
for (const row of token.rows) {
|
||||||
for (const cell of row) {
|
for (const cell of row) {
|
||||||
marked.walkTokens(cell.tokens, callback);
|
values = values.concat(marked.walkTokens(cell.tokens, callback));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'list': {
|
case 'list': {
|
||||||
marked.walkTokens(token.items, callback);
|
values = values.concat(marked.walkTokens(token.items, callback));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
if (marked.defaults.extensions && marked.defaults.extensions.childTokens && marked.defaults.extensions.childTokens[token.type]) { // Walk any extensions
|
if (marked.defaults.extensions && marked.defaults.extensions.childTokens && marked.defaults.extensions.childTokens[token.type]) { // Walk any extensions
|
||||||
marked.defaults.extensions.childTokens[token.type].forEach(function(childTokens) {
|
marked.defaults.extensions.childTokens[token.type].forEach(function(childTokens) {
|
||||||
marked.walkTokens(token[childTokens], callback);
|
values = values.concat(marked.walkTokens(token[childTokens], callback));
|
||||||
});
|
});
|
||||||
} else if (token.tokens) {
|
} else if (token.tokens) {
|
||||||
marked.walkTokens(token.tokens, callback);
|
values = values.concat(marked.walkTokens(token.tokens, callback));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return values;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
162
test/bench.js
vendored
162
test/bench.js
vendored
@ -3,6 +3,7 @@ import { fileURLToPath } from 'url';
|
|||||||
import { isEqual } from './helpers/html-differ.js';
|
import { isEqual } from './helpers/html-differ.js';
|
||||||
import { loadFiles } from './helpers/load.js';
|
import { loadFiles } from './helpers/load.js';
|
||||||
|
|
||||||
|
import { marked as cjsMarked } from '../lib/marked.cjs';
|
||||||
import { marked as esmMarked } from '../lib/marked.esm.js';
|
import { marked as esmMarked } from '../lib/marked.esm.js';
|
||||||
|
|
||||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||||
@ -30,9 +31,10 @@ export function load() {
|
|||||||
export async function runBench(options) {
|
export async function runBench(options) {
|
||||||
options = options || {};
|
options = options || {};
|
||||||
const specs = load();
|
const specs = load();
|
||||||
|
const tests = {};
|
||||||
|
|
||||||
// Non-GFM, Non-pedantic
|
// Non-GFM, Non-pedantic
|
||||||
marked.setOptions({
|
cjsMarked.setOptions({
|
||||||
gfm: false,
|
gfm: false,
|
||||||
breaks: false,
|
breaks: false,
|
||||||
pedantic: false,
|
pedantic: false,
|
||||||
@ -40,9 +42,9 @@ export async function runBench(options) {
|
|||||||
smartLists: false
|
smartLists: false
|
||||||
});
|
});
|
||||||
if (options.marked) {
|
if (options.marked) {
|
||||||
marked.setOptions(options.marked);
|
cjsMarked.setOptions(options.marked);
|
||||||
}
|
}
|
||||||
await bench('cjs marked', specs, marked.parse);
|
tests['cjs marked'] = cjsMarked.parse;
|
||||||
|
|
||||||
esmMarked.setOptions({
|
esmMarked.setOptions({
|
||||||
gfm: false,
|
gfm: false,
|
||||||
@ -54,113 +56,76 @@ export async function runBench(options) {
|
|||||||
if (options.marked) {
|
if (options.marked) {
|
||||||
esmMarked.setOptions(options.marked);
|
esmMarked.setOptions(options.marked);
|
||||||
}
|
}
|
||||||
await bench('esm marked', specs, esmMarked.parse);
|
tests['esm marked'] = esmMarked.parse;
|
||||||
|
|
||||||
// GFM
|
// esmMarked.setOptions({
|
||||||
marked.setOptions({
|
// gfm: true,
|
||||||
gfm: true,
|
// breaks: false,
|
||||||
breaks: false,
|
// pedantic: false,
|
||||||
pedantic: false,
|
// sanitize: false,
|
||||||
sanitize: false,
|
// smartLists: false
|
||||||
smartLists: false
|
// });
|
||||||
});
|
// if (options.marked) {
|
||||||
if (options.marked) {
|
// esmMarked.setOptions(options.marked);
|
||||||
marked.setOptions(options.marked);
|
// }
|
||||||
}
|
// tests['esm marked (gfm)'] = esmMarked.parse;
|
||||||
await bench('cjs marked (gfm)', specs, marked.parse);
|
|
||||||
|
|
||||||
esmMarked.setOptions({
|
|
||||||
gfm: true,
|
|
||||||
breaks: false,
|
|
||||||
pedantic: false,
|
|
||||||
sanitize: false,
|
|
||||||
smartLists: false
|
|
||||||
});
|
|
||||||
if (options.marked) {
|
|
||||||
esmMarked.setOptions(options.marked);
|
|
||||||
}
|
|
||||||
await bench('esm marked (gfm)', specs, esmMarked.parse);
|
|
||||||
|
|
||||||
// Pedantic
|
|
||||||
marked.setOptions({
|
|
||||||
gfm: false,
|
|
||||||
breaks: false,
|
|
||||||
pedantic: true,
|
|
||||||
sanitize: false,
|
|
||||||
smartLists: false
|
|
||||||
});
|
|
||||||
if (options.marked) {
|
|
||||||
marked.setOptions(options.marked);
|
|
||||||
}
|
|
||||||
await bench('cjs marked (pedantic)', specs, marked.parse);
|
|
||||||
|
|
||||||
esmMarked.setOptions({
|
|
||||||
gfm: false,
|
|
||||||
breaks: false,
|
|
||||||
pedantic: true,
|
|
||||||
sanitize: false,
|
|
||||||
smartLists: false
|
|
||||||
});
|
|
||||||
if (options.marked) {
|
|
||||||
esmMarked.setOptions(options.marked);
|
|
||||||
}
|
|
||||||
await bench('esm marked (pedantic)', specs, esmMarked.parse);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await bench('commonmark', specs, (await (async() => {
|
tests.commonmark = (await (async() => {
|
||||||
const { Parser, HtmlRenderer } = await import('commonmark');
|
const { Parser, HtmlRenderer } = await import('commonmark');
|
||||||
const parser = new Parser();
|
const parser = new Parser();
|
||||||
const writer = new HtmlRenderer();
|
const writer = new HtmlRenderer();
|
||||||
return function(text) {
|
return function(text) {
|
||||||
return writer.render(parser.parse(text));
|
return writer.render(parser.parse(text));
|
||||||
};
|
};
|
||||||
})()));
|
})());
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Could not bench commonmark. (Error: %s)', e.message);
|
console.error('Could not bench commonmark. (Error: %s)', e.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await bench('markdown-it', specs, (await (async() => {
|
tests['markdown-it'] = (await (async() => {
|
||||||
const MarkdownIt = (await import('markdown-it')).default;
|
const MarkdownIt = (await import('markdown-it')).default;
|
||||||
const md = new MarkdownIt();
|
const md = new MarkdownIt();
|
||||||
return md.render.bind(md);
|
return md.render.bind(md);
|
||||||
})()));
|
})());
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Could not bench markdown-it. (Error: %s)', e.message);
|
console.error('Could not bench markdown-it. (Error: %s)', e.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await bench(tests, specs);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function bench(name, specs, engine) {
|
export async function bench(tests, specs) {
|
||||||
const before = process.hrtime();
|
const stats = {};
|
||||||
for (let i = 0; i < 1e3; i++) {
|
for (const name in tests) {
|
||||||
for (const spec of specs) {
|
stats[name] = {
|
||||||
await engine(spec.markdown);
|
elapsed: 0n,
|
||||||
}
|
correct: 0
|
||||||
}
|
};
|
||||||
const elapsed = process.hrtime(before);
|
|
||||||
const ms = prettyElapsedTime(elapsed).toFixed();
|
|
||||||
|
|
||||||
let correct = 0;
|
|
||||||
for (const spec of specs) {
|
|
||||||
if (await isEqual(spec.html, await engine(spec.markdown))) {
|
|
||||||
correct++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const percent = (correct / specs.length * 100).toFixed(2);
|
|
||||||
|
|
||||||
console.log('%s completed in %sms and passed %s%', name, ms, percent);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
console.log();
|
||||||
* A simple one-time benchmark
|
for (let i = 0; i < specs.length; i++) {
|
||||||
*/
|
const spec = specs[i];
|
||||||
export async function time(options) {
|
process.stdout.write(`${(i * 100 / specs.length).toFixed(1).padStart(5)}% ${i.toString().padStart(specs.length.toString().length)} of ${specs.length}\r`);
|
||||||
options = options || {};
|
for (const name in tests) {
|
||||||
const specs = load();
|
const test = tests[name];
|
||||||
if (options.marked) {
|
const before = process.hrtime.bigint();
|
||||||
marked.setOptions(options.marked);
|
for (let n = 0; n < 1e3; n++) {
|
||||||
|
await test(spec.markdown);
|
||||||
|
}
|
||||||
|
const after = process.hrtime.bigint();
|
||||||
|
stats[name].elapsed += after - before;
|
||||||
|
stats[name].correct += (await isEqual(spec.html, await test(spec.markdown)) ? 1 : 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const name in tests) {
|
||||||
|
const ms = prettyElapsedTime(stats[name].elapsed);
|
||||||
|
const percent = (stats[name].correct / specs.length * 100).toFixed(2);
|
||||||
|
console.log(`${name} completed in ${ms}ms and passed ${percent}%`);
|
||||||
}
|
}
|
||||||
await bench('marked', specs, marked);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -204,16 +169,6 @@ function parseArg(argv) {
|
|||||||
|
|
||||||
while (argv.length) {
|
while (argv.length) {
|
||||||
const arg = getarg();
|
const arg = getarg();
|
||||||
switch (arg) {
|
|
||||||
case '-t':
|
|
||||||
case '--time':
|
|
||||||
options.time = true;
|
|
||||||
break;
|
|
||||||
case '-m':
|
|
||||||
case '--minified':
|
|
||||||
options.minified = true;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
if (arg.indexOf('--') === 0) {
|
if (arg.indexOf('--') === 0) {
|
||||||
const opt = camelize(arg.replace(/^--(no-)?/, ''));
|
const opt = camelize(arg.replace(/^--(no-)?/, ''));
|
||||||
if (!defaults.hasOwnProperty(opt)) {
|
if (!defaults.hasOwnProperty(opt)) {
|
||||||
@ -232,8 +187,6 @@ function parseArg(argv) {
|
|||||||
} else {
|
} else {
|
||||||
orphans.push(arg);
|
orphans.push(arg);
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (orphans.length > 0) {
|
if (orphans.length > 0) {
|
||||||
@ -257,28 +210,19 @@ function camelize(text) {
|
|||||||
* Main
|
* Main
|
||||||
*/
|
*/
|
||||||
export default async function main(argv) {
|
export default async function main(argv) {
|
||||||
marked = (await import('../lib/marked.cjs')).marked;
|
marked = cjsMarked;
|
||||||
|
|
||||||
const opt = parseArg(argv);
|
const opt = parseArg(argv);
|
||||||
|
|
||||||
if (opt.minified) {
|
|
||||||
marked = (await import('../marked.min.js')).marked;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (opt.time) {
|
|
||||||
await time(opt);
|
|
||||||
} else {
|
|
||||||
await runBench(opt);
|
await runBench(opt);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* returns time to millisecond granularity
|
* returns time to millisecond granularity
|
||||||
|
* @param hrtimeElapsed {bigint}
|
||||||
*/
|
*/
|
||||||
function prettyElapsedTime(hrtimeElapsed) {
|
function prettyElapsedTime(hrtimeElapsed) {
|
||||||
const seconds = hrtimeElapsed[0];
|
return Number(hrtimeElapsed / 1_000_000n);
|
||||||
const frac = Math.round(hrtimeElapsed[1] / 1e3) / 1e3;
|
|
||||||
return seconds * 1e3 + frac;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
process.title = 'marked bench';
|
process.title = 'marked bench';
|
||||||
|
@ -1058,4 +1058,23 @@ br
|
|||||||
});
|
});
|
||||||
expect(marked('*text*').trim()).toBe('<p><em>text walked</em></p>');
|
expect(marked('*text*').trim()).toBe('<p><em>text walked</em></p>');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should wait for async `walkTokens` function', async() => {
|
||||||
|
marked.use({
|
||||||
|
async: true,
|
||||||
|
async walkTokens(token) {
|
||||||
|
if (token.type === 'em') {
|
||||||
|
await new Promise((resolve) => {
|
||||||
|
setTimeout(resolve, 100);
|
||||||
|
});
|
||||||
|
token.text += ' walked';
|
||||||
|
token.tokens = this.Lexer.lexInline(token.text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const promise = marked('*text*');
|
||||||
|
expect(promise).toBeInstanceOf(Promise);
|
||||||
|
const html = await promise;
|
||||||
|
expect(html.trim()).toBe('<p><em>text walked</em></p>');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user