Compare commits

..

No commits in common. "master" and "v1.1.2" have entirely different histories.

30 changed files with 157 additions and 412 deletions

View file

@ -2,11 +2,13 @@
Very fast Markdown parser & HTML renderer implemented in WebAssembly
- Zero dependencies (31 kB gzipped)
- Portable & safe (WASM executes in isolated memory and can run almost anywhere)
- Zero dependencies
- Portable & safe
- [Simple API](#api)
- [Very fast](#benchmarks)
- Based on [md4c](http://github.com/mity/md4c) — compliant to the CommonMark specification
- [Fast and efficient](#benchmarks)
- JS + WASM is only 31 kB gzipped
Based on [md4c](http://github.com/mity/md4c)
## Examples
@ -89,22 +91,29 @@ See [`test/benchmark`](test/benchmark#readme) for more information.
* parse reads markdown source at s and converts it to HTML.
* When output is a byte array, it will be a reference.
*/
export function parse(s :Source, o? :ParseOptions & { bytes? :never|false }) :string
export function parse(s :Source, o? :ParseOptions & { bytes :true }) :Uint8Array
export function parse(s :Source, o? :ParseOptions & { asMemoryView? :never|false }) :string
export function parse(s :Source, o? :ParseOptions & { asMemoryView :true }) :Uint8Array
/** Markdown source code can be provided as a JavaScript string or UTF8 encoded data */
type Source = string | ArrayLike<number>
type Source = string|ArrayLike<number>
/** Options for the parse function */
export interface ParseOptions {
/** Customize parsing. Defaults to ParseFlags.DEFAULT */
/**
* Customize parsing.
* If not provided, the following flags are used, equating to github-style parsing:
* COLLAPSE_WHITESPACE
* PERMISSIVE_ATX_HEADERS
* PERMISSIVE_URL_AUTO_LINKS
* STRIKETHROUGH
* TABLES
* TASK_LISTS
*/
parseFlags? :ParseFlags
/** Select output format. Defaults to "html" */
format? : "html" | "xhtml"
/**
* bytes=true causes parse() to return the result as a Uint8Array instead of a string.
* asMemoryView=true causes parse() to return a view of heap memory as a Uint8Array,
* instead of a string.
*
* The returned Uint8Array is only valid until the next call to parse().
* If you need to keep the returned data around, call Uint8Array.slice() to make a copy,
@ -113,36 +122,9 @@ export interface ParseOptions {
* This only provides a performance benefit when you never need to convert the output
* to a string. In most cases you're better off leaving this unset or false.
*/
bytes? :boolean
/** Allow "javascript:" in links */
allowJSURIs? :boolean
/**
* Optional callback which if provided is called for each code block.
* langname holds the "language tag", if any, of the block.
*
* The returned value is inserted into the resulting HTML verbatim, without HTML escaping.
* Thus, you should take care of properly escaping any special HTML characters.
*
* If the function returns null or undefined, or an exception occurs, the body will be
* included as-is after going through HTML escaping.
*
* Note that use of this callback has an adverse impact on performance as it casues
* calls and data to be bridged between WASM and JS on every invocation.
*/
onCodeBlock? :(langname :string, body :UTF8Bytes) => Uint8Array|string|null|undefined
/** @depreceated use "bytes" instead (v1.1.1) */
asMemoryView? :boolean
}
/** UTF8Bytes is a Uint8Array representing UTF8 text */
export interface UTF8Bytes extends Uint8Array {
/** toString returns a UTF8 decoded string (lazily decoded and cached) */
toString() :string
}
/** Flags that customize Markdown parsing */
export enum ParseFlags {
/** In TEXT, collapse non-trivial whitespace into single ' ' */ COLLAPSE_WHITESPACE,
@ -158,24 +140,13 @@ export enum ParseFlags {
/** Enable tables extension. */ TABLES,
/** Enable task list extension. */ TASK_LISTS,
/** Enable wiki links extension. */ WIKI_LINKS,
/** Enable underline extension (disables '_' for emphasis) */ UNDERLINE,
/** Default flags are:
* COLLAPSE_WHITESPACE |
* PERMISSIVE_ATX_HEADERS |
* PERMISSIVE_URL_AUTO_LINKS |
* STRIKETHROUGH |
* TABLES |
* TASK_LISTS
*/
DEFAULT,
/** Shorthand for NO_HTML_BLOCKS | NO_HTML_SPANS */
NO_HTML,
/** Default flags */ DEFAULT,
/** Shorthand for NO_HTML_BLOCKS | NO_HTML_SPANS */ NO_HTML,
}
```
See [`markdown.d.ts`](markdown.d.ts)
See `markdown.d.ts`
## Building from source

2
dist/markdown.es.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

2
dist/markdown.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

BIN
dist/markdown.wasm vendored Executable file → Normal file

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

View file

@ -13,16 +13,16 @@
<p>line 1
line 2</p>
<h2><a id="code-poetry" class="anchor" aria-hidden="true" href="#code-poetry"></a>Code &amp; Poetry</h2>
<pre><code>YOU CAN ALSO INDENT
BLOCKS TO DISPLAY
CODE OR POETRY.
<pre><code>You can also indent
blocks to display
code or poetry.
INDENTED CODE/POETRY BLOCKS
CAN BE HARD-WRAPPED.
Indented code/poetry blocks
can be hard-wrapped.
</code></pre>
<p><b>Or, wrap your code in three backticks:</b></p>
<pre><code class="language-js">FUNCTION CODEBLOCKS() {
RETURN &quot;CAN BE INSERTED&quot;
<pre><code class="language-js">function codeBlocks() {
return &quot;Can be inserted&quot;
}
</code></pre>
<h3><a id="block-quotes" class="anchor" aria-hidden="true" href="#block-quotes"></a>Block Quotes</h3>
@ -79,4 +79,3 @@ breaks.</p>
<h2><a id="anot-her" class="anchor" aria-hidden="true" href="#anot-her"></a>Anöt######her!</h2>
<h2><a id="anot-her" class="anchor" aria-hidden="true" href="#anot-her"></a>?!Anöt//her!!</h2>
<h2><a id="" class="anchor" aria-hidden="true" href="#"></a>?!!</h2>
<p><a href="">XSS test</a></p>

View file

@ -1,15 +1,8 @@
const fs = require("fs")
const md = require("../dist/markdown.node.js")
// const md = require("../build/debug/markdown.node.js")
const source = fs.readFileSync(__dirname + "/example.md")
const outbuf = md.parse(source, {
bytes: true,
onCodeBlock(lang, body) {
console.log(`onCodeBlock (${lang})`)
return html_escape(body.toString().toUpperCase())
},
})
const outbuf = md.parse(source, { bytes: true })
const outfile = __dirname + "/example.html"
console.log("write", outfile)
fs.writeFileSync(outfile, outbuf)
@ -18,36 +11,16 @@ console.log(fs.readFileSync(outfile, "utf8"))
// mini benchmark
if (process.argv.includes("-bench")) {
benchmark("bytes", {
bytes: true,
})
benchmark("bytes + onCodeBlock", {
bytes: true,
onCodeBlock(lang, body) {
return null // causes the body to be HTML-escaped & included as is
},
})
}
function benchmark(name, options) {
console.log(`benchmark start ${name} (sampling ~2s of data)`)
console.log("benchmark start (sampling ~2s of data)")
const timeStart = Date.now()
const N = 1000, T = 2000
let ntotal = 0
while (Date.now() - timeStart < 2000) {
for (let i = 0; i < N; i++) {
global["dont-optimize-away"] = md.parse(source, options)
global["dont-optimize-away"] = md.parse(source, { bytes: true })
}
ntotal += N
}
const timeSpent = Date.now() - timeStart
console.log(
`benchmark end ${name} -- avg parse time: ` +
`${((timeSpent / ntotal) * 1000).toFixed(1)}us`)
}
function html_escape(str) {
return str.replace(/[&<>'"]/g, tag => ({
'&': '&amp;','<': '&lt;','>': '&gt;',"'": '&#39;','"': '&quot;'
}[tag]))
console.log(`benchmark end -- avg parse time: ${((timeSpent / ntotal) * 1000).toFixed(1)}us`)
}

View file

@ -82,5 +82,3 @@ function codeBlocks() {
## ?!Anöt//her!!
## ?!!
[XSS test](javAscRipt:alert("xss"))

24
markdown.d.ts vendored
View file

@ -28,34 +28,10 @@ export interface ParseOptions {
*/
bytes? :boolean
/** Allow "javascript:" in links */
allowJSURIs? :boolean
/**
* Optional callback which if provided is called for each code block.
* langname holds the "language tag", if any, of the block.
*
* The returned value is inserted into the resulting HTML verbatim, without HTML escaping.
* Thus, you should take care of properly escaping any special HTML characters.
*
* If the function returns null or undefined, or an exception occurs, the body will be
* included as-is after going through HTML escaping.
*
* Note that use of this callback has an adverse impact on performance as it casues
* calls and data to be bridged between WASM and JS on every invocation.
*/
onCodeBlock? :(langname :string, body :UTF8Bytes) => Uint8Array|string|null|undefined
/** @depreceated use "bytes" instead (v1.1.1) */
asMemoryView? :boolean
}
/** UTF8Bytes is a Uint8Array representing UTF8 text */
export interface UTF8Bytes extends Uint8Array {
/** toString returns a UTF8 decoded string (lazily decoded and cached) */
toString() :string
}
/** Flags that customize Markdown parsing */
export enum ParseFlags {
/** In TEXT, collapse non-trivial whitespace into single ' ' */ COLLAPSE_WHITESPACE,

28
package-lock.json generated
View file

@ -1,31 +1,13 @@
{
"name": "markdown-wasm",
"version": "1.2.0",
"lockfileVersion": 2,
"version": "1.1.2",
"lockfileVersion": 1,
"requires": true,
"packages": {
"": {
"version": "1.2.0",
"license": "MIT",
"devDependencies": {
"wasmc": "^2.2.1"
}
},
"node_modules/wasmc": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/wasmc/-/wasmc-2.2.1.tgz",
"integrity": "sha512-uV/nhZdfnffBfLv2WJWWfRoJwk+5phhPobg7fqnJoRpXdUV5eShRb6tOXoco987cTxarkBymYwGufMdC4dPXAw==",
"dev": true,
"bin": {
"wasmc": "wasmc"
}
}
},
"dependencies": {
"wasmc": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/wasmc/-/wasmc-2.2.1.tgz",
"integrity": "sha512-uV/nhZdfnffBfLv2WJWWfRoJwk+5phhPobg7fqnJoRpXdUV5eShRb6tOXoco987cTxarkBymYwGufMdC4dPXAw==",
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/wasmc/-/wasmc-2.2.0.tgz",
"integrity": "sha512-Pp2B42WuhL7JjslKDlGbpoV8WYpGAz0ct1XYTQWIN99cVsj7nfEuhD/jKwivLJHOFSOzMZZqvQH4m5BPQmTmCg==",
"dev": true
}
}

View file

@ -1,6 +1,6 @@
{
"name": "markdown-wasm",
"version": "1.2.0",
"version": "1.1.2",
"description": "Markdown parser and html generator implemented in WebAssembly",
"browser": "dist/markdown.js",
"main": "dist/markdown.node.js",
@ -35,6 +35,6 @@
"author": "Rasmus Andersson <https://rsms.me/>",
"license": "MIT",
"devDependencies": {
"wasmc": "^2.2.1"
"wasmc": "^2.2.0"
}
}

View file

@ -54,21 +54,7 @@ typedef int32_t i32;
#endif
#if DEBUG
#include <stdio.h>
#define dlog(fmt, ...) printf(fmt "\n", ##__VA_ARGS__)
#define dlog(...) printf(__VA_ARGS__)
#else
#define dlog(...)
#endif /* DEBUG > 0 */
#include "wbuf.h"
// these should be in sync with "OutputFlags" in md.js
typedef enum OutputFlags {
OutputFlagHTML = 1 << 0,
OutputFlagXHTML = 1 << 1,
OutputFlagAllowJSURI = 1 << 2, // allow "javascript:" URIs in links
} OutputFlags;
typedef int(*JSTextFilterFun)(
const char* metaptr, u32 metalen,
const char* inptr, u32 inlen,
const char** outptrp); // return outlen

View file

@ -25,18 +25,17 @@
#include <string.h>
#include <ctype.h>
#include <strings.h>
#include "common.h"
#include "fmt_html.h"
#include "md4c.h"
// typedef struct FmtHTML_st {
// WBuf* outbuf;
// int imgnest;
// int addanchor;
// u32 flags;
// } FmtHTML;
typedef struct HtmlRenderer_st {
WBuf* outbuf;
int imgnest;
int addanchor;
u32 flags;
} HtmlRenderer;
static char htmlEscapeMap[256] = {
@ -62,20 +61,20 @@ static char htmlEscapeMap[256] = {
static const char ucReplacementUTF8[] = { 0xef, 0xbf, 0xbd };
static inline void render_text(FmtHTML* r, const char* pch, size_t len) {
static inline void render_text(HtmlRenderer* r, const char* pch, size_t len) {
WBufAppendBytes(r->outbuf, pch, len);
}
static inline void render_literal(FmtHTML* r, const char* cs) {
static inline void render_literal(HtmlRenderer* r, const char* cs) {
WBufAppendBytes(r->outbuf, cs, strlen(cs));
}
static inline void render_char(FmtHTML* r, char c) {
static inline void render_char(HtmlRenderer* r, char c) {
WBufAppendc(r->outbuf, c);
}
static void render_html_escaped(FmtHTML* r, const char* data, size_t size) {
static void render_html_escaped(HtmlRenderer* r, const char* data, size_t size) {
MD_OFFSET beg = 0;
MD_OFFSET off = 0;
@ -168,7 +167,7 @@ static size_t WBufAppendSlug(WBuf* b, const char* pch, size_t len) {
}
static void render_attribute(FmtHTML* r, const MD_ATTRIBUTE* attr) {
static void render_attribute(HtmlRenderer* r, const MD_ATTRIBUTE* attr) {
int i;
for (i = 0; attr->substr_offsets[i] < attr->size; i++) {
MD_TEXTTYPE type = attr->substr_types[i];
@ -184,7 +183,7 @@ static void render_attribute(FmtHTML* r, const MD_ATTRIBUTE* attr) {
}
static void render_open_ol_block(FmtHTML* r, const MD_BLOCK_OL_DETAIL* det) {
static void render_open_ol_block(HtmlRenderer* r, const MD_BLOCK_OL_DETAIL* det) {
if (det->start == 1) {
render_literal(r, "<ol>\n");
} else {
@ -194,7 +193,7 @@ static void render_open_ol_block(FmtHTML* r, const MD_BLOCK_OL_DETAIL* det) {
}
}
static void render_open_li_block(FmtHTML* r, const MD_BLOCK_LI_DETAIL* det) {
static void render_open_li_block(HtmlRenderer* r, const MD_BLOCK_LI_DETAIL* det) {
if (det->is_task) {
render_literal(r, "<li class=\"task-list-item\"><input type=\"checkbox\" disabled");
if (det->task_mark == 'x' || det->task_mark == 'X') {
@ -206,7 +205,7 @@ static void render_open_li_block(FmtHTML* r, const MD_BLOCK_LI_DETAIL* det) {
}
}
static void render_open_code_block(FmtHTML* r, const MD_BLOCK_CODE_DETAIL* det) {
static void render_open_code_block(HtmlRenderer* r, const MD_BLOCK_CODE_DETAIL* det) {
render_literal(r, "<pre><code");
if (det->lang.text != NULL) {
render_literal(r, " class=\"language-");
@ -214,41 +213,9 @@ static void render_open_code_block(FmtHTML* r, const MD_BLOCK_CODE_DETAIL* det)
render_char(r, '"');
}
render_char(r, '>');
r->codeBlockNest++;
}
static void render_close_code_block(FmtHTML* r, const MD_BLOCK_CODE_DETAIL* det) {
dlog("end code block (lang \"%.*s\")", (int)det->lang.size, det->lang.text);
r->codeBlockNest--;
if (r->onCodeBlock) {
const char* text = r->tmpbuf.start;
size_t len = WBufLen(&r->tmpbuf);
int outlen = -1;
if (len <= 0x7FFFFFFF) {
const char* outptr = NULL;
outlen = r->onCodeBlock(det->lang.text, (u32)det->lang.size, text, (u32)len, &outptr);
if (outlen > 0 && outptr != NULL)
WBufAppendBytes(r->outbuf, outptr, (size_t)outlen);
if (outptr != NULL)
free((void*)outptr);
}
if (outlen < 0) {
// The function failed or opted out of taking care of formatting
render_html_escaped(r, text, len);
}
WBufReset(&r->tmpbuf);
}
render_literal(r, "</code></pre>\n");
}
static void render_open_td_block(FmtHTML* r, bool isTH, const MD_BLOCK_TD_DETAIL* det) {
static void render_open_td_block(HtmlRenderer* r, bool isTH, const MD_BLOCK_TD_DETAIL* det) {
render_text(r, isTH ? "<th" : "<td", 3);
switch (det->align) {
case MD_ALIGN_LEFT: render_literal(r, " align=\"left\">"); break;
@ -258,21 +225,9 @@ static void render_open_td_block(FmtHTML* r, bool isTH, const MD_BLOCK_TD_DETAIL
}
}
static bool is_javascript_uri(const MD_CHAR* text, size_t len) {
return (
len >= strlen("javascript:") &&
strncasecmp(text, "javascript:", strlen("javascript:")) == 0
);
}
static void render_open_a_span(FmtHTML* r, const MD_SPAN_A_DETAIL* det) {
static void render_open_a_span(HtmlRenderer* r, const MD_SPAN_A_DETAIL* det) {
render_literal(r, "<a href=\"");
// skip "javascript:" URIs unless explicitly allowed
if ((r->flags & OutputFlagAllowJSURI) != 0 ||
!is_javascript_uri(det->href.text, det->href.size))
{
render_attribute(r, &det->href);
}
render_attribute(r, &det->href);
if (det->title.text != NULL) {
render_literal(r, "\" title=\"");
render_attribute(r, &det->title);
@ -280,23 +235,23 @@ static void render_open_a_span(FmtHTML* r, const MD_SPAN_A_DETAIL* det) {
render_literal(r, "\">");
}
static void render_open_img_span(FmtHTML* r, const MD_SPAN_IMG_DETAIL* det) {
static void render_open_img_span(HtmlRenderer* r, const MD_SPAN_IMG_DETAIL* det) {
render_literal(r, "<img src=\"");
render_attribute(r, &det->src);
render_literal(r, "\" alt=\"");
r->imgnest++;
}
static void render_close_img_span(FmtHTML* r, const MD_SPAN_IMG_DETAIL* det) {
static void render_close_img_span(HtmlRenderer* r, const MD_SPAN_IMG_DETAIL* det) {
if(det->title.text != NULL) {
render_literal(r, "\" title=\"");
render_attribute(r, &det->title);
}
render_literal(r, (r->flags & OutputFlagXHTML) ? "\"/>" : "\">");
render_literal(r, (r->flags & MD_HTML_FLAG_XHTML) ? "\"/>" : "\">");
r->imgnest--;
}
static void render_open_wikilink_span(FmtHTML* r, const MD_SPAN_WIKILINK_DETAIL* det) {
static void render_open_wikilink_span(HtmlRenderer* r, const MD_SPAN_WIKILINK_DETAIL* det) {
render_literal(r, "<x-wikilink data-target=\"");
render_attribute(r, &det->target);
render_literal(r, "\">");
@ -311,30 +266,30 @@ static void render_open_wikilink_span(FmtHTML* r, const MD_SPAN_WIKILINK_DETAIL*
static int enter_block_callback(MD_BLOCKTYPE type, void* detail, void* userdata) {
static const MD_CHAR* head[6] = { "<h1>", "<h2>", "<h3>", "<h4>", "<h5>", "<h6>" };
FmtHTML* r = (FmtHTML*) userdata;
HtmlRenderer* r = (HtmlRenderer*) userdata;
switch(type) {
case MD_BLOCK_DOC: /* noop */ break;
case MD_BLOCK_QUOTE: render_literal(r, "<blockquote>\n"); break;
case MD_BLOCK_UL: render_literal(r, "<ul>\n"); break;
case MD_BLOCK_OL: render_open_ol_block(r, (const MD_BLOCK_OL_DETAIL*)detail); break;
case MD_BLOCK_LI: render_open_li_block(r, (const MD_BLOCK_LI_DETAIL*)detail); break;
case MD_BLOCK_HR: render_literal(r, (r->flags & OutputFlagXHTML) ? "<hr/>\n" : "<hr>\n"); break;
case MD_BLOCK_DOC: /* noop */ break;
case MD_BLOCK_QUOTE: render_literal(r, "<blockquote>\n"); break;
case MD_BLOCK_UL: render_literal(r, "<ul>\n"); break;
case MD_BLOCK_OL: render_open_ol_block(r, (const MD_BLOCK_OL_DETAIL*)detail); break;
case MD_BLOCK_LI: render_open_li_block(r, (const MD_BLOCK_LI_DETAIL*)detail); break;
case MD_BLOCK_HR: render_literal(r, (r->flags & MD_HTML_FLAG_XHTML) ? "<hr/>\n" : "<hr>\n"); break;
case MD_BLOCK_H:
{
render_literal(r, head[((MD_BLOCK_H_DETAIL*)detail)->level - 1]);
r->addanchor = 1;
break;
}
case MD_BLOCK_CODE: render_open_code_block(r, (const MD_BLOCK_CODE_DETAIL*) detail); break;
case MD_BLOCK_HTML: /* noop */ break;
case MD_BLOCK_P: render_literal(r, "<p>"); break;
case MD_BLOCK_TABLE: render_literal(r, "<table>\n"); break;
case MD_BLOCK_THEAD: render_literal(r, "<thead>\n"); break;
case MD_BLOCK_TBODY: render_literal(r, "<tbody>\n"); break;
case MD_BLOCK_TR: render_literal(r, "<tr>\n"); break;
case MD_BLOCK_TH: render_open_td_block(r, true, (MD_BLOCK_TD_DETAIL*)detail); break;
case MD_BLOCK_TD: render_open_td_block(r, false, (MD_BLOCK_TD_DETAIL*)detail); break;
case MD_BLOCK_CODE: render_open_code_block(r, (const MD_BLOCK_CODE_DETAIL*) detail); break;
case MD_BLOCK_HTML: /* noop */ break;
case MD_BLOCK_P: render_literal(r, "<p>"); break;
case MD_BLOCK_TABLE: render_literal(r, "<table>\n"); break;
case MD_BLOCK_THEAD: render_literal(r, "<thead>\n"); break;
case MD_BLOCK_TBODY: render_literal(r, "<tbody>\n"); break;
case MD_BLOCK_TR: render_literal(r, "<tr>\n"); break;
case MD_BLOCK_TH: render_open_td_block(r, true, (MD_BLOCK_TD_DETAIL*)detail); break;
case MD_BLOCK_TD: render_open_td_block(r, false, (MD_BLOCK_TD_DETAIL*)detail); break;
}
return 0;
@ -342,32 +297,32 @@ static int enter_block_callback(MD_BLOCKTYPE type, void* detail, void* userdata)
static int leave_block_callback(MD_BLOCKTYPE type, void* detail, void* userdata) {
static const MD_CHAR* head[6] = { "</h1>\n", "</h2>\n", "</h3>\n", "</h4>\n", "</h5>\n", "</h6>\n" };
FmtHTML* r = (FmtHTML*) userdata;
HtmlRenderer* r = (HtmlRenderer*) userdata;
switch(type) {
case MD_BLOCK_DOC: /*noop*/ break;
case MD_BLOCK_QUOTE: render_literal(r, "</blockquote>\n"); break;
case MD_BLOCK_UL: render_literal(r, "</ul>\n"); break;
case MD_BLOCK_OL: render_literal(r, "</ol>\n"); break;
case MD_BLOCK_LI: render_literal(r, "</li>\n"); break;
case MD_BLOCK_HR: /*noop*/ break;
case MD_BLOCK_H: render_literal(r, head[((MD_BLOCK_H_DETAIL*)detail)->level - 1]); break;
case MD_BLOCK_CODE: render_close_code_block(r, (const MD_BLOCK_CODE_DETAIL*)detail); break;
case MD_BLOCK_HTML: /* noop */ break;
case MD_BLOCK_P: render_literal(r, "</p>\n"); break;
case MD_BLOCK_TABLE: render_literal(r, "</table>\n"); break;
case MD_BLOCK_THEAD: render_literal(r, "</thead>\n"); break;
case MD_BLOCK_TBODY: render_literal(r, "</tbody>\n"); break;
case MD_BLOCK_TR: render_literal(r, "</tr>\n"); break;
case MD_BLOCK_TH: render_literal(r, "</th>\n"); break;
case MD_BLOCK_TD: render_literal(r, "</td>\n"); break;
case MD_BLOCK_DOC: /*noop*/ break;
case MD_BLOCK_QUOTE: render_literal(r, "</blockquote>\n"); break;
case MD_BLOCK_UL: render_literal(r, "</ul>\n"); break;
case MD_BLOCK_OL: render_literal(r, "</ol>\n"); break;
case MD_BLOCK_LI: render_literal(r, "</li>\n"); break;
case MD_BLOCK_HR: /*noop*/ break;
case MD_BLOCK_H: render_literal(r, head[((MD_BLOCK_H_DETAIL*)detail)->level - 1]); break;
case MD_BLOCK_CODE: render_literal(r, "</code></pre>\n"); break;
case MD_BLOCK_HTML: /* noop */ break;
case MD_BLOCK_P: render_literal(r, "</p>\n"); break;
case MD_BLOCK_TABLE: render_literal(r, "</table>\n"); break;
case MD_BLOCK_THEAD: render_literal(r, "</thead>\n"); break;
case MD_BLOCK_TBODY: render_literal(r, "</tbody>\n"); break;
case MD_BLOCK_TR: render_literal(r, "</tr>\n"); break;
case MD_BLOCK_TH: render_literal(r, "</th>\n"); break;
case MD_BLOCK_TD: render_literal(r, "</td>\n"); break;
}
return 0;
}
static int enter_span_callback(MD_SPANTYPE type, void* detail, void* userdata) {
FmtHTML* r = (FmtHTML*) userdata;
HtmlRenderer* r = (HtmlRenderer*) userdata;
if(r->imgnest > 0) {
/* We are inside a Markdown image label. Markdown allows to use any
@ -392,8 +347,8 @@ static int enter_span_callback(MD_SPANTYPE type, void* detail, void* userdata) {
case MD_SPAN_EM: render_literal(r, "<em>"); break;
case MD_SPAN_STRONG: render_literal(r, "<b>"); break;
case MD_SPAN_U: render_literal(r, "<u>"); break;
case MD_SPAN_A: render_open_a_span(r, (MD_SPAN_A_DETAIL*)detail); break;
case MD_SPAN_IMG: render_open_img_span(r, (MD_SPAN_IMG_DETAIL*)detail); break;
case MD_SPAN_A: render_open_a_span(r, (MD_SPAN_A_DETAIL*) detail); break;
case MD_SPAN_IMG: render_open_img_span(r, (MD_SPAN_IMG_DETAIL*) detail); break;
case MD_SPAN_CODE: render_literal(r, "<code>"); break;
case MD_SPAN_DEL: render_literal(r, "<del>"); break;
case MD_SPAN_LATEXMATH: render_literal(r, "<x-equation>"); break;
@ -405,7 +360,7 @@ static int enter_span_callback(MD_SPANTYPE type, void* detail, void* userdata) {
}
static int leave_span_callback(MD_SPANTYPE type, void* detail, void* userdata) {
FmtHTML* r = (FmtHTML*) userdata;
HtmlRenderer* r = (HtmlRenderer*) userdata;
if(r->imgnest > 0) {
/* Ditto as in enter_span_callback(), except we have to allow the
@ -432,12 +387,7 @@ static int leave_span_callback(MD_SPANTYPE type, void* detail, void* userdata) {
}
static int text_callback(MD_TEXTTYPE type, const MD_CHAR* text, MD_SIZE size, void* userdata) {
FmtHTML* r = (FmtHTML*) userdata;
if (r->codeBlockNest && r->onCodeBlock) {
WBufAppendBytes(&r->tmpbuf, text, size);
return 0;
}
HtmlRenderer* r = (HtmlRenderer*) userdata;
if (r->addanchor) {
r->addanchor = 0;
@ -459,18 +409,18 @@ static int text_callback(MD_TEXTTYPE type, const MD_CHAR* text, MD_SIZE size, vo
}
}
switch (type) {
switch(type) {
case MD_TEXT_NULLCHAR: render_text(r, ucReplacementUTF8, sizeof(ucReplacementUTF8)); break;
case MD_TEXT_BR:
render_literal(
r,
r->imgnest == 0 ?
((r->flags & OutputFlagXHTML) ? "<br/>\n" : "<br>\n") :
((r->flags & MD_HTML_FLAG_XHTML) ? "<br/>\n" : "<br>\n") :
" "
);
break;
render_literal(r, (r->flags & OutputFlagXHTML) ? "<hr/>\n" : "<hr>\n"); break;
render_literal(r, (r->flags & MD_HTML_FLAG_XHTML) ? "<hr/>\n" : "<hr>\n"); break;
case MD_TEXT_SOFTBR: render_literal(r, (r->imgnest == 0 ? "\n" : " ")); break;
case MD_TEXT_HTML: render_text(r, text, size); break;
@ -485,15 +435,18 @@ static int text_callback(MD_TEXTTYPE type, const MD_CHAR* text, MD_SIZE size, vo
// dlog("MD4C: %s\n", msg);
// }
int fmt_html(const MD_CHAR* input, MD_SIZE input_size, FmtHTML* fmt) {
fmt->imgnest = 0;
fmt->addanchor = 0;
fmt->codeBlockNest = 0;
fmt->tmpbuf = (WBuf){0};
int fmt_html(
const MD_CHAR* input,
MD_SIZE input_size,
WBuf* outbuf,
u32 parser_flags,
u32 render_flags
) {
HtmlRenderer render = { outbuf, 0, 0, render_flags };
MD_PARSER parser = {
0,
fmt->parserFlags,
parser_flags,
enter_block_callback,
leave_block_callback,
enter_span_callback,
@ -503,11 +456,5 @@ int fmt_html(const MD_CHAR* input, MD_SIZE input_size, FmtHTML* fmt) {
NULL
};
WBufInit(&fmt->tmpbuf);
int res = md_parse(input, input_size, &parser, (void*)fmt);
WBufFree(&fmt->tmpbuf);
return res;
return md_parse(input, input_size, &parser, (void*) &render);
}

View file

@ -1,18 +1,6 @@
#pragma once
#include "wbuf.h"
typedef struct FmtHTML {
OutputFlags flags;
u32 parserFlags; // passed along to md_parse
WBuf* outbuf;
#define MD_HTML_FLAG_XHTML 0x0008 // instead of e.g. <br>, generate <br/>
// optional callbacks
JSTextFilterFun onCodeBlock;
// internal state
int imgnest;
int addanchor;
int codeBlockNest;
WBuf tmpbuf;
} FmtHTML;
int fmt_html(const char* input, u32 inputlen, FmtHTML* fmt);
int fmt_html(const char* input, u32 inputlen, WBuf* outbuf, u32 parserFlags, u32 renderFlags);

View file

@ -1,9 +1,16 @@
#include <ctype.h>
#include "common.h"
#include "wlib.h"
#include "wbuf.h"
#include "fmt_html.h"
// #include "fmt_json.h"
// these should be in sync with "OutputFlags" in md.js
typedef enum OutputFlags {
OutputFlagHTML = 1 << 0,
OutputFlagXHTML = 1 << 1,
} OutputFlags;
typedef enum ErrorCode {
ERR_NONE,
ERR_MD_PARSE,
@ -13,7 +20,7 @@ typedef enum ErrorCode {
#if DEBUG
void __attribute__((constructor)) init() {
dlog("WASM INIT");
dlog("WASM INIT\n");
}
#endif
@ -28,24 +35,21 @@ export size_t parseUTF8(
u32 inbuflen,
u32 parser_flags,
OutputFlags outflags,
const char** outptr,
JSTextFilterFun onCodeBlock
const char** outptr
) {
dlog("parseUTF8 called with inbufptr=%p inbuflen=%u", inbufptr, inbuflen);
dlog("parseUTF8 called with inbufptr=%p inbuflen=%u\n", inbufptr, inbuflen);
WBufReset(&outbuf);
if ((outflags & OutputFlagHTML) || (outflags & OutputFlagXHTML)) {
if (outflags & OutputFlagHTML) {
WBufReserve(&outbuf, inbuflen * 2); // approximate output size to minimize reallocations
FmtHTML fmt = {
.flags = outflags,
.parserFlags = parser_flags,
.outbuf = &outbuf,
.onCodeBlock = onCodeBlock,
};
u32 render_flags = 0;
if (outflags & OutputFlagXHTML) {
render_flags |= MD_HTML_FLAG_XHTML;
}
if (fmt_html(inbufptr, inbuflen, &fmt) != 0) {
if (fmt_html(inbufptr, inbuflen, &outbuf, parser_flags, render_flags) != 0) {
// fmt_html returns status of md_parse which only fails in extreme cases
// like when out of memory. md4c does not provide error codes or error messages.
WErrSet(ERR_MD_PARSE, "md parser error");

View file

@ -1,10 +1,4 @@
import {
utf8,
withTmpBytePtr,
withOutPtr,
werrCheck,
mallocbuf,
} from "./wlib"
import { utf8, withTmpBytePtr, withOutPtr, werrCheck } from "./wlib"
export const ready = Module.ready
@ -41,14 +35,12 @@ export const ParseFlags = {
NO_HTML: 0x0020 | 0x0040, // NO_HTML_BLOCKS | NO_HTML_SPANS
}
// these should be in sync with "OutputFlags" in common.h
// these should be in sync with "OutputFlags" in md.c
const OutputFlags = {
HTML: 1 << 0, // Output HTML
XHTML: 1 << 1, // Output XHTML (only has effect with HTML flag set)
AllowJSURI: 1 << 2, // Allow "javascript:" URIs
HTML: 1 << 0, // Output HTML
XHTML: 1 << 1, // Output XHTML (only has effect with HTML flag set)
}
export function parse(source, options) {
options = options || {}
@ -57,8 +49,7 @@ export function parse(source, options) {
options.parseFlags
)
let outputFlags = options.allowJSURIs ? OutputFlags.AllowJSURI : 0
let outputFlags = 0
switch (options.format) {
case "xhtml":
outputFlags |= OutputFlags.HTML | OutputFlags.XHTML
@ -75,16 +66,11 @@ export function parse(source, options) {
throw new Error(`invalid format "${options.format}"`)
}
let onCodeBlockPtr = options.onCodeBlock ? create_onCodeBlock_fn(options.onCodeBlock) : 0
let buf = as_byte_array(source)
let buf = typeof source == "string" ? utf8.encode(source) : source
let outbuf = withOutPtr(outptr => withTmpBytePtr(buf, (inptr, inlen) =>
_parseUTF8(inptr, inlen, parseFlags, outputFlags, outptr, onCodeBlockPtr)
_parseUTF8(inptr, inlen, parseFlags, outputFlags, outptr)
))
if (options.onCodeBlock)
removeFunction(onCodeBlockPtr)
// check for error and throw if needed
werrCheck()
@ -93,62 +79,8 @@ export function parse(source, options) {
// console.log(utf8.decode(outbuf))
// }
if (options.bytes || options.asMemoryView)
if (options.bytes || options.asMemoryView) {
return outbuf
}
return utf8.decode(outbuf)
}
function create_onCodeBlock_fn(onCodeBlock) {
// See https://emscripten.org/docs/porting/connecting_cpp_and_javascript/
// Interacting-with-code.html#calling-javascript-functions-as-function-pointers-from-c
//
// Function's C type: JSTextFilterFun
// (metaptr ptr, metalen ptr, inptr ptr, inlen ptr, outptr ptr) -> outlen int
const fnptr = addFunction(function(metaptr, metalen, inptr, inlen, outptr) {
try {
// lang is the "language" tag, if any, provided with the code block
const lang = metalen > 0 ? utf8.decode(HEAPU8.subarray(metaptr, metaptr + metalen)) : ""
// body is a view into heap memory of the segment of source (UTF8 bytes)
const body = HEAPU8.subarray(inptr, inptr + inlen)
let bodystr = undefined
body.toString = () => (bodystr || (bodystr = utf8.decode(body)))
// result is the result from the onCodeBlock function
let result = null
result = onCodeBlock(lang, body)
if (result === null || result === undefined) {
// Callback indicates that it does not wish to filter.
// The md.c implementation will html-encode the body.
return -1
}
let resbuf = as_byte_array(result)
if (resbuf.length > 0) {
// copy resbuf to WASM heap memory
const resptr = mallocbuf(resbuf, resbuf.length)
// write pointer value
HEAPU32[outptr >> 2 /* == outptr / 4 */] = resptr
// Note: fmt_html.c calls free(resptr)
}
return resbuf.length
} catch (err) {
console.error(`error in markdown onCodeBlock callback: ${err.stack||err}`)
return -1
}
}, "iiiiii")
return fnptr
}
function as_byte_array(something) {
if (typeof something == "string")
return utf8.encode(something)
if (something instanceof Uint8Array)
return something
return new Uint8Array(something)
}

View file

@ -1,4 +1,4 @@
#include "common.h"
#include "wbuf.h"
void WBufInit(WBuf* b) {
b->start = 0;

View file

@ -1,4 +1,5 @@
#pragma once
#include "common.h"
typedef struct WBuf_s {
char* start; // pointer to start of data

View file

@ -6,7 +6,6 @@ const Path = require('path')
const commonmark = require('commonmark')
const Showdown = require('showdown')
const marked = require('marked')
const { Remarkable } = require('remarkable');
const markdownit = require('markdown-it')('commonmark')
const markdown_wasm = require('../../dist/markdown.node.js')
@ -23,9 +22,6 @@ var showdown = new Showdown.Converter()
var parser = new commonmark.Parser()
var renderer = new commonmark.HtmlRenderer()
// setup remarkable
var remarkable = new Remarkable();
// parse CLI input
let filename = process.argv[2]
if (!filename) {
@ -80,9 +76,6 @@ function benchmarkFile(benchfile) {
.add('marked', function() {
marked(contents);
})
.add('remarkable', function() {
remarkable.render(contents);
})
.add('markdown-it', function() {
markdownit.render(contents);
})

View file

@ -18,7 +18,6 @@
"d3-node": "^2.2.2",
"markdown-it": "^10.0.0",
"marked": "^0.7.0",
"remarkable": "^2.0.1",
"showdown": "^1.9.1",
"svgo": "^1.3.2"
}

View file

@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="840" height="300" font-family="-apple-system,BlinkMacSystemFont,&quot;Segoe UI&quot;,Helvetica,Arial,sans-serif" font-size="16"><g text-anchor="end" font-weight="500"><text alignment-baseline="central" x="156" y="68" dx="-10">markdown-wasm</text><text alignment-baseline="central" x="156" y="104" dx="-10">remarkable</text><text alignment-baseline="central" x="156" y="140" dx="-10">markdown-it</text><text alignment-baseline="central" x="156" y="176" dx="-10">marked</text><text alignment-baseline="central" x="156" y="212" dx="-10">commonmark</text><text alignment-baseline="central" x="156" y="248" dx="-10">showdown</text></g><path fill="#4e79a7" d="M156 52h668v32H156z"/><path fill="#f28e2c" d="M156 88h453.666v32H156z"/><path fill="#e15759" d="M156 124h300.527v32H156z"/><path fill="#76b7b2" d="M156 160h259.363v32H156z"/><path fill="#59a14f" d="M156 196h210.906v32H156z"/><path fill="#edc949" d="M156 232h30.517v32H156z"/><g fill="#fff" text-anchor="end" font-weight="500"><text x="824" y="68" alignment-baseline="central" dx="-5">136,888</text><text x="609.666" y="104" alignment-baseline="central" dx="-5">92,966</text><text x="456.527" y="140" alignment-baseline="central" dx="-5">61,584</text><text x="415.363" y="176" alignment-baseline="central" dx="-5">53,149</text><text x="366.906" y="212" alignment-baseline="central" dx="-5">43,219</text><text x="186.517" y="248" alignment-baseline="central" dx="5" fill="#000" text-anchor="start">6,254</text></g><g fill="none" font-size="12.8" text-anchor="middle" opacity=".5"><g class="tick"><path stroke="currentColor" d="M156.5 48v-6"/><text fill="currentColor" y="-9" dy="0em" transform="translate(156.5 48)">0</text></g><g class="tick"><path stroke="currentColor" d="M254.098 48v-6"/><text fill="currentColor" y="-9" dy="0em" transform="translate(254.098 48)">20,000</text></g><g class="tick"><path stroke="currentColor" d="M351.697 48v-6"/><text fill="currentColor" y="-9" dy="0em" transform="translate(351.697 48)">40,000</text></g><g class="tick"><path stroke="currentColor" d="M449.295 48v-6"/><text fill="currentColor" y="-9" dy="0em" transform="translate(449.295 48)">60,000</text></g><g class="tick"><path stroke="currentColor" d="M546.893 48v-6"/><text fill="currentColor" y="-9" dy="0em" transform="translate(546.893 48)">80,000</text></g><g class="tick"><path stroke="currentColor" d="M644.491 48v-6"/><text fill="currentColor" y="-9" dy="0em" transform="translate(644.491 48)">100,000</text></g><g class="tick"><path stroke="currentColor" d="M742.09 48v-6"/><text fill="currentColor" y="-9" dy="0em" transform="translate(742.09 48)">120,000</text></g></g></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="840" height="264" font-family="-apple-system,BlinkMacSystemFont,&quot;Segoe UI&quot;,Helvetica,Arial,sans-serif" font-size="16"><g text-anchor="end" font-weight="500"><text alignment-baseline="central" x="156" y="68" dx="-10">markdown-wasm</text><text alignment-baseline="central" x="156" y="104" dx="-10">markdown-it</text><text alignment-baseline="central" x="156" y="140" dx="-10">marked</text><text alignment-baseline="central" x="156" y="176" dx="-10">commonmark</text><text alignment-baseline="central" x="156" y="212" dx="-10">showdown</text></g><path fill="#4e79a7" d="M156 52h668v32H156z"/><path fill="#f28e2c" d="M156 88h290.307v32H156z"/><path fill="#e15759" d="M156 124h263.486v32H156z"/><path fill="#76b7b2" d="M156 160h226.65v32H156z"/><path fill="#59a14f" d="M156 196h29.116v32H156z"/><g fill="#fff" text-anchor="end" font-weight="500"><text x="824" y="68" alignment-baseline="central" dx="-5">195,065</text><text x="446.307" y="104" alignment-baseline="central" dx="-5">84,774</text><text x="419.486" y="140" alignment-baseline="central" dx="-5">76,942</text><text x="382.65" y="176" alignment-baseline="central" dx="-5">66,185</text><text x="185.116" y="212" alignment-baseline="central" dx="5" fill="#000" text-anchor="start">8,502</text></g><g fill="none" font-size="12.8" text-anchor="middle" opacity=".5"><g class="tick"><path stroke="currentColor" d="M156.5 48v-6"/><text fill="currentColor" y="-9" dy="0em" transform="translate(156.5 48)">0</text></g><g class="tick"><path stroke="currentColor" d="M224.99 48v-6"/><text fill="currentColor" y="-9" dy="0em" transform="translate(224.99 48)">20,000</text></g><g class="tick"><path stroke="currentColor" d="M293.48 48v-6"/><text fill="currentColor" y="-9" dy="0em" transform="translate(293.48 48)">40,000</text></g><g class="tick"><path stroke="currentColor" d="M361.97 48v-6"/><text fill="currentColor" y="-9" dy="0em" transform="translate(361.97 48)">60,000</text></g><g class="tick"><path stroke="currentColor" d="M430.46 48v-6"/><text fill="currentColor" y="-9" dy="0em" transform="translate(430.46 48)">80,000</text></g><g class="tick"><path stroke="currentColor" d="M498.95 48v-6"/><text fill="currentColor" y="-9" dy="0em" transform="translate(498.95 48)">100,000</text></g><g class="tick"><path stroke="currentColor" d="M567.44 48v-6"/><text fill="currentColor" y="-9" dy="0em" transform="translate(567.44 48)">120,000</text></g><g class="tick"><path stroke="currentColor" d="M635.93 48v-6"/><text fill="currentColor" y="-9" dy="0em" transform="translate(635.93 48)">140,000</text></g><g class="tick"><path stroke="currentColor" d="M704.42 48v-6"/><text fill="currentColor" y="-9" dy="0em" transform="translate(704.42 48)">160,000</text></g><g class="tick"><path stroke="currentColor" d="M772.909 48v-6"/><text fill="currentColor" y="-9" dy="0em" transform="translate(772.909 48)">180,000</text></g></g></svg>

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

View file

@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="840" height="300" font-family="-apple-system,BlinkMacSystemFont,&quot;Segoe UI&quot;,Helvetica,Arial,sans-serif" font-size="16"><path fill="#4e79a7" d="M45 52h483v32H45z"/><path fill="#f28e2c" d="M16 88h582v32H16z"/><path fill="#76b7b2" d="M38 124h584v32H38z"/><path fill="#e15759" d="M47 160h578v32H47z"/><path fill="#59a14f" d="M96 196h553v32H96z"/><path fill="#edc949" d="M258 232h566v32H258z"/><g fill="#fff" font-weight="500" text-anchor="middle"><text x="286.5" y="68" alignment-baseline="central">markdown-wasm</text><text x="307" y="104" alignment-baseline="central">remarkable</text><text x="330" y="140" alignment-baseline="central">marked</text><text x="336" y="176" alignment-baseline="central">markdown-it</text><text x="372.5" y="212" alignment-baseline="central">commonmark</text><text x="541" y="248" alignment-baseline="central">showdown</text></g><g fill="#fff" font-weight="500"><text x="45" y="68" alignment-baseline="central" dx="5">2.7us</text><text x="16" y="104" alignment-baseline="central" dx="5">1.8us</text><text x="38" y="140" alignment-baseline="central" dx="5">2.5us</text><text x="47" y="176" alignment-baseline="central" dx="5">2.8us</text><text x="96" y="212" alignment-baseline="central" dx="5">5.7us</text><text x="258" y="248" alignment-baseline="central" dx="5">59.6us</text></g><g fill="#fff" font-weight="500" text-anchor="end"><text x="528" y="68" alignment-baseline="central" dx="-5">3.1ms</text><text x="598" y="104" alignment-baseline="central" dx="-5">8.4ms</text><text x="622" y="140" alignment-baseline="central" dx="-5">12.0ms</text><text x="625" y="176" alignment-baseline="central" dx="-5">12.5ms</text><text x="649" y="212" alignment-baseline="central" dx="-5">17.9ms</text><text x="824" y="248" alignment-baseline="central" dx="-5">226.4ms</text></g><g fill="none" font-size="12.8" text-anchor="middle" opacity=".5"><path stroke="currentColor" d="M24.5 48v-6M52.5 48v-6M72.5 48v-6M87.5 48v-6M100.5 48v-6M111.5 48v-6M120.5 48v-6M128.5 48v-6"/><g class="tick"><path stroke="currentColor" d="M135.5 48v-6"/><text fill="currentColor" y="-9" dy="0em" transform="translate(135.5 48)">10.0us</text></g><path stroke="currentColor" d="M183.5 48v-6M211.5 48v-6M230.5 48v-6M246.5 48v-6M258.5 48v-6M269.5 48v-6M278.5 48v-6M286.5 48v-6"/><g class="tick"><path stroke="currentColor" d="M293.5 48v-6"/><text fill="currentColor" y="-9" dy="0em" transform="translate(293.5 48)">100.0us</text></g><path stroke="currentColor" d="M341.5 48v-6M369.5 48v-6M388.5 48v-6M404.5 48v-6M416.5 48v-6M427.5 48v-6M436.5 48v-6M444.5 48v-6"/><g class="tick"><path stroke="currentColor" d="M451.5 48v-6"/><text fill="currentColor" y="-9" dy="0em" transform="translate(451.5 48)">1000.0us</text></g><path stroke="currentColor" d="M499.5 48v-6M527.5 48v-6M547.5 48v-6M562.5 48v-6M575.5 48v-6M585.5 48v-6M594.5 48v-6M602.5 48v-6"/><g class="tick"><path stroke="currentColor" d="M610.5 48v-6"/><text fill="currentColor" y="-9" dy="0em" transform="translate(610.5 48)">10.0ms</text></g><path stroke="currentColor" d="M657.5 48v-6M685.5 48v-6M705.5 48v-6M720.5 48v-6M733.5 48v-6M743.5 48v-6M753.5 48v-6M761.5 48v-6"/><g class="tick"><path stroke="currentColor" d="M768.5 48v-6"/><text fill="currentColor" y="-9" dy="0em" transform="translate(768.5 48)">100.0ms</text></g><path stroke="currentColor" d="M815.5 48v-6"/></g></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="840" height="264" font-family="-apple-system,BlinkMacSystemFont,&quot;Segoe UI&quot;,Helvetica,Arial,sans-serif" font-size="16"><path fill="#4e79a7" d="M21 52h486v32H21z"/><path fill="#e15759" d="M16 88h576v32H16z"/><path fill="#f28e2c" d="M21 124h573v32H21z"/><path fill="#76b7b2" d="M70 160h541v32H70z"/><path fill="#59a14f" d="M227 196h597v32H227z"/><g fill="#fff" font-weight="500" text-anchor="middle"><text x="264" y="68" alignment-baseline="central">markdown-wasm</text><text x="304" y="104" alignment-baseline="central">marked</text><text x="307.5" y="140" alignment-baseline="central">markdown-it</text><text x="340.5" y="176" alignment-baseline="central">commonmark</text><text x="525.5" y="212" alignment-baseline="central">showdown</text></g><g fill="#fff" font-weight="500"><text x="21" y="68" alignment-baseline="central" dx="5">2.0us</text><text x="16" y="104" alignment-baseline="central" dx="5">1.8us</text><text x="21" y="140" alignment-baseline="central" dx="5">2.0us</text><text x="70" y="176" alignment-baseline="central" dx="5">4.0us</text><text x="227" y="212" alignment-baseline="central" dx="5">40.8us</text></g><g fill="#fff" font-weight="500" text-anchor="end"><text x="507" y="68" alignment-baseline="central" dx="-5">2.5ms</text><text x="592" y="104" alignment-baseline="central" dx="-5">8.6ms</text><text x="594" y="140" alignment-baseline="central" dx="-5">8.9ms</text><text x="611" y="176" alignment-baseline="central" dx="-5">11.4ms</text><text x="824" y="212" alignment-baseline="central" dx="-5">262.8ms</text></g><g fill="none" font-size="12.8" text-anchor="middle" opacity=".5"><path stroke="currentColor" d="M22.5 48v-6M50.5 48v-6M69.5 48v-6M85.5 48v-6M97.5 48v-6M108.5 48v-6M117.5 48v-6M125.5 48v-6"/><g class="tick"><path stroke="currentColor" d="M132.5 48v-6"/><text fill="currentColor" y="-9" dy="0em" transform="translate(132.5 48)">10.0us</text></g><path stroke="currentColor" d="M179.5 48v-6M207.5 48v-6M226.5 48v-6M241.5 48v-6M254.5 48v-6M264.5 48v-6M273.5 48v-6M281.5 48v-6"/><g class="tick"><path stroke="currentColor" d="M288.5 48v-6"/><text fill="currentColor" y="-9" dy="0em" transform="translate(288.5 48)">100.0us</text></g><path stroke="currentColor" d="M336.5 48v-6M363.5 48v-6M383.5 48v-6M398.5 48v-6M410.5 48v-6M421.5 48v-6M430.5 48v-6M438.5 48v-6"/><g class="tick"><path stroke="currentColor" d="M445.5 48v-6"/><text fill="currentColor" y="-9" dy="0em" transform="translate(445.5 48)">1000.0us</text></g><path stroke="currentColor" d="M492.5 48v-6M520.5 48v-6M539.5 48v-6M555.5 48v-6M567.5 48v-6M577.5 48v-6M586.5 48v-6M594.5 48v-6"/><g class="tick"><path stroke="currentColor" d="M602.5 48v-6"/><text fill="currentColor" y="-9" dy="0em" transform="translate(602.5 48)">10.0ms</text></g><path stroke="currentColor" d="M649.5 48v-6M676.5 48v-6M696.5 48v-6M711.5 48v-6M724.5 48v-6M734.5 48v-6M743.5 48v-6M751.5 48v-6"/><g class="tick"><path stroke="currentColor" d="M758.5 48v-6"/><text fill="currentColor" y="-9" dy="0em" transform="translate(758.5 48)">100.0ms</text></g><path stroke="currentColor" d="M805.5 48v-6"/></g></svg>

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

View file

@ -23,10 +23,6 @@ const m = {
] : [
// release flags
]),
lflags: [
// force inclusion of addFunction & removeFunction in release builds (emcc bug?)
"-s","EXPORTED_RUNTIME_METHODS=addFunction,removeFunction",
],
constants: {
VERSION: package.version,
},