-
Notifications
You must be signed in to change notification settings - Fork 3.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
async renderer support #458
Comments
Is there any case where marked actually needs to perform an async operation during compilation? |
what do you mean by "compilation"? |
The operation of turning markdown into HTML. |
if rendering requires to call a function that is not synchronous or this function will run for a while. this will be similar to how code highlighting works. making all rendering functions async will be more consistent. |
My use case is this: i want to override |
+1 for this, I need some enhanced custom markdown that will fetch data from the server to get the info to render the final HTML. e.g. |
+1 for this I define a special markdown syntax for our editor the syntas like this:
the number is a id, I should fetch data by ajax (http://x.x.x.x/?id=1120),and then i will convert the data to html , but marked not support async What should I do? I think this solution is pretty good #798 |
Async renderers would be very helpful! My use case is turning a markdown image responsive by fetching multiple import marked from 'marked'
const renderer = {
async image(href, title, text) {
if (href?.includes(`mycms.tld`) && !href.endsWith(`.svg`)) {
const id = href.split(`myCmsId`)[1]
const query = `{
asset(id: "${id}") {
width
height
}
}`
const { asset } = await fetchFromCms(query)
const { width, height } = asset
title = title ? `title="${title}"` : ``
return `
<picture>
<source media="(min-width: 1000px)" type="image/webp" srcset="${href}?w=1000"&fm=webp"" />
<source media="(min-width: 800px)" type="image/webp" srcset="${href}?w=800"&fm=webp"" />
<source media="(min-width: 600px)" type="image/webp" srcset="${href}?w=600"&fm=webp"" />
<source media="(min-width: 400px)" type="image/webp" srcset="${href}?w=400"&fm=webp"" />
<img src="${href}?w=1000" alt="${text}" ${title} loading="lazy" width="${width}" height="${height}" style="width: 100%; height: auto;" />
</picture>`
}
return false // delegate to default marked image renderer
},
}
marked.use({ renderer }) |
This is on the v2 project board but does not have a PR yet. If someone would like to start a PR this would go faster. |
@UziTech I'm happy to try it if pointed in the right direction. |
basically we just need to change /src/Parser.js to call the renderers asynchronously without slowing down the parser. |
So it's just adding |
Just awaiting every render call will probably slow down the parser quite a bit. |
I see. But what’s the alternative?
|
Not sure. That is what needs to be figured out to get async renderers working. |
Would it make sense to have both a sync and an async renderer (the latter being undefined by default) and only using the async one if the user defined it? import marked from 'marked'
const renderer = {
async imageAsync(href, title, text) {
// ...
},
}
marked.use({ renderer }) Similar to how |
That could work. Or maybe have an I don't think it should be a problem with async being slightly slower since any async action (api call, file system call, etc.) will be a much bigger bottleneck for speed than awaiting a synchronous function. I just don't want to slow down the users that don't need it to be async. |
You mean like this try {
// ...
if (opt.async) return await Parser.parse(tokens, opt);
else return Parser.parse(tokens, opt);
} catch (e) { ... } |
Ya it might work better to create a separate parser so we don't have the conditional logic on each renderer call. try {
// ...
if (opt.async) return await AsyncParser.parse(tokens, opt);
else return Parser.parse(tokens, opt);
} catch (e) { ... } |
It will be very useful have async support in renderer (speaking as an author of static site generator that uses Marked) There is a plenty of examples:
|
I'm also building a static site that uses Marked. My use case is that I'd like to execute code in the code blocks and render the output in my HTML page. I need async capabilities in my renderer to call a child process or remote host. |
I have made an async renderer extension for markdownit in my project called
decharge, I'll extract it when I have some free time:)
Probably a similar method can be used for marked.
Until I extract it, feel free to take some ideas, it's located at
examples/project-website/src/utils/async-render-markdown.ts
…On Thu, Mar 10, 2022, 8:59 PM Andy ***@***.***> wrote:
I'm also building a static site that uses Marked. My use case is that I'd
like to execute code in the code blocks and render the output in my HTML
page. I need async capabilities in my renderer to call a child process or
remote host.
—
Reply to this email directly, view it on GitHub
<#458 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AEMIERCJOLTATBHJHZR3OWTU7JIA7ANCNFSM4ASHCU6Q>
.
Triage notifications on the go with GitHub Mobile for iOS
<https://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675>
or Android
<https://play.google.com/store/apps/details?id=com.github.android&referrer=utm_campaign%3Dnotification-email%26utm_medium%3Demail%26utm_source%3Dgithub>.
You are receiving this because you are subscribed to this thread.Message
ID: ***@***.***>
|
I started implementing a solution for this in #2405 |
I would like to lazy import syntax highlighting library only when needed. looking at this example: marked.setOptions({
highlight: function(code, lang, callback) {
require('pygmentize-bundled') ({ lang: lang, format: 'html' }, code, function (err, result) {
callback(err, result.toString());
});
}
});
marked.parse(markdownString, (err, html) => {
console.log(html);
}); would be nice to do: marked.setOptions({
async highlight (code, lang) {
const xyz = await import('...')
return xyz(code, lang)
}
})
await marked.parse(markdownString) |
it's also possible to look at the function if it's a async function: async function f() {}
f.constructor.name === 'AsyncFunction' // true so based on that you could do some if/else logic... but i guess this would be bad for sync functions that return promises or have some thenable function found this if somebody is intrested: |
as part of making it async, i would like to wish for something like a async stream to output data to the user as data flows in, i would like to do this within service worker: self.onfetch = evt => {
evt.respondWith(async () => {
const res = await fetch('markdown.md')
const asyncIterable1 = res.body.pipeThrough(new TextDecoderStream())
const asyncIterable2 = marked.parse(asyncIterable1, {
async highlight(code, lang) {
return await xyz()
}
})
const rs = ReadableStream.from(asyncIterable2)
return new Response(rs, { headers: { 'content-type': 'text/html' } })
})
} I ask that you do support asyncIterable (Symbol.asyncIterator) as an input parameter so it can work interchangeable between both whatwg and node streams so that it isn't tied to any core features. |
instead of having a async/sync method you could instead do things with // Uses symbol.asyncIterator and promises
for await (const chunk of marked.parse(readable)) { ... }
// Uses symbol.iterator (chunk may return promise if a plugin only supports asyncIterator)
for (const chunk of marked.parse(readable)) { ... } It could maybe also be possible to have something like: const iterable = marked.parse(something, {
highlight * (code, lang) {
yield someSyncTransformation(code, lang)
}
})
for (const str of iterable) { ... }
// or
const iterable = marked.parse(something, {
async highlight * (code, lang) {
const transformer = await import(lib)
yield transformer(code, lang)
}
})
for (const chunk of iterable) {
if (typeof chunk === 'string') console.log(chunk)
else console.log(await chunk)
} it would be still possible for marked to still only be sync but it would be up to the developer to either use |
The biggest problem with making marked asynchronous in any of these ways is that most users won't use it but it will still slow it down for them. Even #2405 is about 5% slower because it needs to load more files. And not everything is async yet. The only way that I can think to do this with keeping markeds speed for synchronous users is to have a separate entry point. Something like The biggest problem with that approach is that most of the code for marked will have to be duplicated and at that point it would almost be easier to just create a separate package that imports marked just for the parts that aren't duplicated (probably just the rules). |
I don't think we necessary have to make the marked library async, if we could experimenting with creating a sync iterable if we have markdown like
then it would have to yield [ for (const chunk of parser) {
await chunk
} // another form of possible solution could be:
const chunks = [...parse(markdown)] // [string, promise<string>, string]
const result = await Promise.all(chunks)
const html = result.join('') |
@jimmywarting if you want to create a PR with your proposal I would be interested to see how that will work. I don't know if that would solve the issue of not having duplicate code since most users would still just want to do |
hmm, haven't touched the underlying codebase or investigated the underlying code of this package or anything - just coming up with proposals 😄 |
🎉 This issue has been resolved in version 4.1.0 🎉 The release is available on: Your semantic-release bot 📦🚀 |
It is helpful if one needs to use http to retrieve rich data for rendering.
The text was updated successfully, but these errors were encountered: