scalameta / mdoc

Typechecked markdown documentation for Scala
https://scalameta.org/mdoc/
Apache License 2.0
392 stars 80 forks source link

PostModifier for ScalaJS #538

Open fdietze opened 3 years ago

fdietze commented 3 years ago

For a dom dsl, I would like to show, which html code will be generated:

div("hello")

using a PostModifier, I want to render it into a temporary html element and extract the innerHTML of that element.

But as far as I understand, the PostModifier runs only on the JVM, so this is currently not possible.

Any ideas?

fdietze commented 3 years ago

Or is it possible to wrap the code with other code before execution in the browser? That would also be an option for me.

olafurpg commented 3 years ago

Thank you for reporting! The Scala.js support is implemented with an entirely separate compile/evaluate pipeline making it difficult to support JS/JVM with a unified API. A JS PostModifier would need to evaluate inside the browser every time HTML gets rendered while a JVM PostModifier evaluates once before the output markdown is generated.

Can you elaborate on your use-case? What would you concretely like to write in the markdown source and what should concrete render in the output?

olafurpg commented 3 years ago

Or is it possible to wrap the code with other code before execution in the browser? That would also be an option for me.

Is it an option to manually wrap the code with a function call? For example, if you have an innerHTML helper method that performs the logic for you:

```scala mdoc:js
innerHTML {
  div("hello")
}
fdietze commented 3 years ago

I started to work on the outwatch documentation again and want to automatically generate the corresponding html code for static content:

https://github.com/outwatch/outwatch#concatenating-strings

It doesn't matter much if it's evaluated at build-time with node or in the browser

(I'm on mobile now and can add some code later)

Yes, wrapping with a visible shared function call would have been my next feasible workaround.

olafurpg commented 3 years ago

Have you considered writing an invisible helper method called “div” that implements your desired functionality?

fdietze commented 3 years ago

Have you considered writing an invisible helper method called “div” that implements your desired functionality?

Very interesting idea!

This is what I tried:

```scala mdoc:js:shared:invisible
def div(modifiers: VDomModifier*):String = {
    val vnode = dsl.div(modifiers)

    val domNode = org.scalajs.dom.document.createElement("div")
    OutWatch.renderInto[IO](domNode, vnode).unsafeRunSync()
    domNode.innerHTML
}
```

```scala mdoc:js
div("Hello ", "World!", color := "tomato")
```

But then I realized that return values are not printed in mdoc:js. I was only able to write the result into the current node:

```scala mdoc:js
node.innerText = div("Hello ", "World!", color := "tomato")
```

I cannot put the node.innerText = ... inside the invisible function, since it would be a different node, right? edit: I just tried. when :shared is used, there is no more node variable in scope.


Originally, I imagined having a custom modifier vnode like this:

```scala mdoc:js:vnode
div("Hello ", "World!", color := "tomato")
```

Which adds a comment, containing the innerHTML representation:

```scala
div("Hello ", "World!", color := "tomato")
// <div style="color: tomato;">Hello World!</div>
```
olafurpg commented 3 years ago

One thing we could do is to make the node variable available through a global var or an implicit. Then you could update innerHTML. Do you think that would resolve your usecase?

fdietze commented 3 years ago

Yes, I think either would solve my case. An implicit sounds more elegant to me. Would there be any downsides?

olafurpg commented 3 years ago

Sounds good. I opened #539 to add a new implicit val nodeImplicit: HTMLElementImplicit in scope for every code fence that you can pass around to invisible helper methods.

Atry commented 1 year ago

I think the use case would be to use the output of mdoc:js as a snapshot test, i.e., the test should be considered failed if the generated output changes.

fdietze commented 1 year ago

For reference, this is what I ended up with in the outwatch docs: