Skip to content
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

feature: support using unified as a template tag. #40

Closed

Conversation

ChristianMurphy
Copy link
Member

@ChristianMurphy ChristianMurphy commented Nov 4, 2018

Inspired by hyperHTML and lit-html.
This PR adds new templateTag and templateTagSync functions.

These new functions allow unified to be applied as a tag/modifier function to any template string.

Simple Example:

var unified = require('unified')
var markdown = require('remark-parse')
var remark2rehype = require('remark-rehype')
var doc = require('rehype-document')
var format = require('rehype-format')
var stringify = require('rehype-stringify')

var html = unified()
  .use(markdown)
  .use(remark2rehype)
  .use(doc)
  .use(format)
  .use(stringify)
  .templateTagSync

console.log(html`# Hello world!`.toString())

Yields:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
  </head>
  <body>
    <h1>Hello world!</h1>
  </body>
</html>

Interpolation Example:

var unified = require('unified')
var markdown = require('remark-parse')
var remark2rehype = require('remark-rehype')
var doc = require('rehype-document')
var format = require('rehype-format')
var stringify = require('rehype-stringify')

var html = unified()
  .use(markdown)
  .use(remark2rehype)
  .use(doc)
  .use(format)
  .use(stringify)
  .templateTagSync

var file = html`
# Hello world!
${['First', 'Second', 'Third'].map(header => `## ${header} subsection`).join('\n')}
`

console.log(file.toString())

Yields:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
  </head>
  <body>
    <h1>Hello world!</h1>
    <h2>First subsection</h2>
    <h2>Second subsection</h2>
    <h2>Third subsection</h2>
  </body>
</html>

@codecov-io

This comment has been minimized.

@wooorm
Copy link
Member

wooorm commented Nov 4, 2018

Wow, this is super interesting!

I’m mainly wondering though, why do this? As in, your first example could be rewritten like so:

var unified = require('unified')
var markdown = require('remark-parse')
var remark2rehype = require('remark-rehype')
var doc = require('rehype-document')
var format = require('rehype-format')
var html = require('rehype-stringify')

var html = unified()
  .use(markdown)
  .use(remark2rehype)
  .use(doc)
  .use(format)
  .use(html)
  .processSync

console.log(html(`# Hello world!`).toString())

Note the processSync and the parens. As they’re so similar, what’s the benefit of having this?

@ChristianMurphy
Copy link
Member Author

ChristianMurphy commented Nov 4, 2018

Template tags have a different signature than process expects.
For example:

console.log`1 ${2} 3 ${4}`
// [ "1 ", " 3 ", "" ]
// 2
// 4

Templates will take the static text and turn it into an array.
And takes interpolated values and spreads those across the other parameters.

This new function applies the String.raw helper which normalizes the values to something process can consume.

console.log(String.raw`1 ${2} 3 ${4}`)
// '1 2 3 4'

@wooorm
Copy link
Member

wooorm commented Nov 4, 2018

Right I understand that part, but for your example, one could also do console.log(`1 ${2} 3 ${4}`), which yields the same as the last one with String.raw.

I get that it’s new and interesting, and maybe that’s good enough, but I don’t see much added value over calling a function ;)

@ChristianMurphy
Copy link
Member Author

ChristianMurphy commented Nov 4, 2018

As it stands it's mostly syntax sugar to avoid needing (). 🙂

Where it could go is more interesting, similar to how MDX allows adding non-markdown content mid-document, supporting template literals opens up the possibility of being able to mixin new content types mid-document into any unified supported language.

@wooorm
Copy link
Member

wooorm commented Nov 4, 2018

@ChristianMurphy Right! So it gets interesting when it would be possible to “evaluate” the tag and the template in a build step?

@ChristianMurphy
Copy link
Member Author

ChristianMurphy commented Nov 4, 2018

My thought would be to use data type of interpolated values to determine how to evaluate.
typeof string would evaluate using current parser.
typeof object | function would switch to a different processing track.

@ChristianMurphy
Copy link
Member Author

ChristianMurphy commented Nov 4, 2018

typeof object | function would switch to a different processing track

In the alternative track, unified would parse the content surrounding the interpolated value, then add the interpolated value directly into the AST.

E.G.

var unified = require('unified')
var markdown = require('remark-parse')
var remark2rehype = require('remark-rehype')
var doc = require('rehype-document')
var format = require('rehype-format')
var html = require('rehype-stringify')
const h = require('hastscript')

var md2ht = unified()
  .use(markdown)
  .use(remark2rehype)
  .use(doc)
  .use(format)
  .use(html)
  .templateTagSync

md2ht`## ${h('samp', 'sample header')}`

would generate

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
  </head>
  <body>
    <h2>
      <samp>
        sample header
      </samp>
    </h2>
  </body>
</html>

@wooorm
Copy link
Member

wooorm commented Nov 4, 2018

@ChristianMurphy Huhh, very interesting indeed! I like where this is heading. But I foresee that a) this feature would change in the future, as b) we’re not entirely sure how this would work yet.

It could of course be that first we need the implementation before we can create interesting cases, but I feel like it’s better to develop both together. Develop both the implementation and the use-cases together?

@ChristianMurphy
Copy link
Member Author

I feel like it’s better to develop both together. Develop both the implementation and the use-cases together?

I agree.
Personally, I see this as the first step toward being able try out potential use cases.
If it should start as an external wrapper for unified, rather than a core processor function, that's fine.
This PR is mostly to get a discussion started.

@wooorm
Copy link
Member

wooorm commented Nov 4, 2018

@Hypercubed interested in hearing your thoughts on this!

@Hypercubed
Copy link

Hypercubed commented Nov 5, 2018

It's very interesting when you add the value interpolation, as you discussed. In lit-html they have directives that can do may cool things like handling async functions. At the moment I have a couple of comments:

  1. The unified transformations are static usually with a stringification at the end. If the final result is text I don't really see directives being a help. This is unlike lit-html where directives can do interesting things when rending the dom. lit-html keeps track of static and dynamic parts of the template to allow efficient DOM updates. This simply has no benefit when generating static strings.

  2. In lit-html the static and dynamic parts of the template are handled very differently. The static parts must be complete HTML elements (for example html<h1>${name} + html<h2> doesn't work). You cannot use dynamic content to generate elements (for example html<${tag}>${name}<${tag}>) for this reason and also because the dynamic parts are HTML escaped. The reason is, as discussed in my point Component/duo support, try catch? #1, lit-html needs to separate static and dynamic parts for efficient updates.

  3. In your example md2ht## ${h('samp', 'sample header')} you are mixing md and HTML (actually HAST) this can be an issue. The function would need to know the final product is HTML and mixing syntax leads would lead to issues I would image.

All this said I think it would be very interesting if unified was able to convert to something like lit-html... then using a unified md2lit template method would be 💯

@ChristianMurphy
Copy link
Member Author

Thanks for the feedback @wooorm and @Hypercubed!

something like lit-html... then using a unified md2lit template method would be 💯

It would be.
For that, it'd be nice to have incremental parsing, allowing more targeted updates of content.

@wooorm wooorm added the 💬 type/discussion This is a request for comments label Aug 10, 2019
@ChristianMurphy ChristianMurphy deleted the feat/template-tag branch March 6, 2020 12:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
💬 type/discussion This is a request for comments
Development

Successfully merging this pull request may close these issues.

None yet

4 participants