posthtml / posthtml-components

A PostHTML plugin for creating components with HTML-friendly syntax inspired by Laravel Blade. Slots, stack/push, props, custom tag and much more.
https://posthtml.github.io/posthtml-components/
MIT License
13 stars 1 forks source link

Using with async plugins like posthtml-fetch #21

Open cossssmin opened 1 year ago

cossssmin commented 1 year ago

Hey, thanks so much for creating this, I'm currently testing it in Maizzle as a replacement for posthtml-modules.

Tried using it with posthtml-fetch:

const posthtml = require('posthtml')
const fetchPlugin = require('posthtml-fetch')
const components = require('posthtml-component')

posthtml([
  components({
    root: './',
    folders: ['src'],
    tagName: 'component',
    attribute: 'src',
    yield: 'content',
    expressions: {strictMode: false},
    plugins: [
      fetchPlugin()
    ],
  }),
])

... but I get a TypeError:

TypeError: tree.match is not a function
    at F:\DEV\x\node_modules\posthtml-fetch\lib\index.js:19:10     
    at new Promise (<anonymous>)
    at F:\DEV\x\node_modules\posthtml-fetch\lib\index.js:16:10     
    at F:\DEV\x\node_modules\posthtml-component\src\index.js:272:12
    at Array.reduce (<anonymous>)
    at applyPluginsToTree (F:\DEV\x\node_modules\posthtml-component\src\index.js:271:18)
    at F:\DEV\x\node_modules\posthtml-component\src\index.js:156:14
    at F:\DEV\x\node_modules\posthtml-component\src\index.js:129:27
    at err (F:\DEV\x\node_modules\posthtml\lib\index.js:219:34)    
    at tryCatch (F:\DEV\x\node_modules\posthtml\lib\index.js:300:12

Any way we can get them to work?

thewebartisan7 commented 1 year ago

Hi Cosmin,

The problem you are facing can be quickly fixed, but there are others issues that need to be solved.

For the error tree.match is not a function we could just use the same solution used by posthtml-include here https://github.com/posthtml/posthtml-include/blob/master/lib/index.js#L16 where the function match is added if not defined:

    const {match} = require('posthtml/lib/api');
    // ...
    tree.match = tree.match || match;

I have the same issue with markdown it plugin which I am using, and I have propose here the fix https://github.com/posthtml/posthtml-markdownit/pull/26

However, the main problem is that the async plugin like posthtml-fetch will not works how currently passed plugins process the tree. I suppose the same issue is with posthtml-extend but not with posthtml-modules since it process plugins using Promise.

For fix this I think is required several refactors how the plugin process the tree. Maybe I will check into, but not sure yet when I will get time soon.

There is a solution that I used in this case, it's not ideal, but it works with plugins I tested, including the posthtml-fetch, is to load the posthtml-fetch after the posthtml-component process whole tree.

Example:

const posthtml = require('posthtml')
const fetchPlugin = require('posthtml-fetch')
const components = require('posthtml-component')

posthtml([
  components({
    root: './',
    folders: ['src'],
    tagName: 'component',
    attribute: 'src',
    yield: 'content',
    expressions: {strictMode: false},
  }),
  fetchPlugin()
])

Another thing you need to do for avoid that posthtml-expressions process the response local before the fetch plugin is to add @ like below:

    <fetch url="https://jsonplaceholder.typicode.com/users/1">
        @{{ response.name }}'s username is @{{ response.username }}
    </fetch>

In this way after posthtml-component process whole tree you will get below which can be then processed by posthtml-expressions plugin loaded by fetch plugin.

    <fetch url="https://jsonplaceholder.typicode.com/users/1">
        {{ response.name }}'s username is {{ response.username }}
    </fetch>

It did test and it works also with x-tag inside fetch, example:

<!-- user.html component -->
<script props>
    module.exports = {
      name: props.name || 'Default name',
      username: props.username || 'Default username',
    }
</script>

<div>
    {{ name }}'s username is {{ username }}
</div>

Usage with fetch:


<fetch url="https://jsonplaceholder.typicode.com/users/2">
    <x-user name="@{{ response.name }}" username="@{{ response.username }}"></x-user>
</fetch>

I know it's not ideal, but I hope this will works for you.

The most annoying thing is to prefix the locals with @.

I will think a bit more about processing async plugins and let you know.

cossssmin commented 1 year ago

Hmm, I think I've got it working like this, by running posthtml-fetch before posthtml-component instead of passing it as a plugin:

posthtml([
  fetchPlugin({expressions: {...expressionsOptions, locals}}),
  components({
    root: './',
    folders: ['src/components', 'src/layouts', 'src/templates'],
    tagName: 'component',
    attribute: 'src',
    yield: 'content',
    expressions: {...expressionsOptions, locals},
  }),
])
  .process(html, {...posthtmlOptions})
  .then(result => result.html)

Works without having to add @ to expressions in attributes, though in order to ignore them I need to do @@, like:

<fetch url="https://jsonplaceholder.typicode.com/users/2">
    <x-user name="@@{{ response.name }}"></x-user>
</fetch>

... but that might just something on my end, not sure yet.

But yeah, would be cool to be able to just pass posthtml-fetch as a plugin to posthtml-component. Regarding this, I've also noticed the tree.render method needs setting as well, in addition to tree.match.

thewebartisan7 commented 1 year ago

But if you pass fetch plugin before components, then <fetch> tag inside nested components will not be processed.

I mean if you have a page like:

<html>
  <body>
    <!-- This fetch tag will be processed -->
    <fetch url="https://jsonplaceholder.typicode.com/users/2">
        <x-user name="@@{{ response.name }}"></x-user>
    </fetch>

    <!-- If this component has a fetch tag it will not be processed -->
   <x-component-with-fetch> </x-component-with-fetch>
  </body>
</html>

A solution for this is to load before and after the component plugin like:

posthtml([
  fetchPlugin({expressions: {...expressionsOptions, locals}}),
  components({
    root: './',
    folders: ['src/components', 'src/layouts', 'src/templates'],
    tagName: 'component',
    attribute: 'src',
    yield: 'content',
    expressions: {...expressionsOptions, locals},
  }),

 fetchPlugin({expressions: {...expressionsOptions, locals}}),
])
  .process(html, {...posthtmlOptions})
  .then(result => result.html)

But I think is just enough one time after the component.

Works without having to add @ to expressions in attributes, though in order to ignore them I need to do @@, like:

The only reason for this could be that somewhere in the code the expressions plugin process twice the locals, so first time it remove the @ and second time it process it again by another plugin that use expression plugin. Make sense?

Another solution for avoid to use @ is to set custom delimiter only for fetch plugin, example:

  fetchPlugin({expressions: {delimiters: ['[[', ']]'], locals}}), 

Then:

<fetch url="https://jsonplaceholder.typicode.com/users/2">
    <x-user name="[[ response.name ]]"></x-user>
</fetch>

This way the locals inside fetch plugin will be only processed by expression plugin initialized by fetch.

But yeah, would be cool to be able to just pass posthtml-fetch as a plugin to posthtml-component.

Yes that would be the best solution. I regret that I didn't do this before, but I remember that I had some issues when I tried. Will check again.

Regarding this, I've also noticed the tree.render method needs setting as well, in addition to tree.match.

Not sure what you mean, which setting need?

Maybe I got what you mean. I think this could be fixed like for match:

    const render = require('posthtml-render');
    // ...
    tree.render = tree.render || render;

But maybe the best solution would be just to fix this in posthtml-component plugin by wrapping tree in posthtml() function, something like posthtml-modules is doing here https://github.com/posthtml/posthtml-modules/blob/master/index.js#L125

cossssmin commented 1 year ago

Not sure what you mean, which setting need?

Similar to your solution in posthtml-markdownit, otherwise it's also undefined.

thewebartisan7 commented 1 year ago

Not sure what you mean, which setting need?

Similar to your solution in posthtml-markdownit, otherwise it's also undefined.

Maybe you have missed but I have edited my reply about this.

I think this could be fixed like for match:

    const render = require('posthtml-render');
    // ...
    tree.render = tree.render || render;

But the best solution would be just to fix this in posthtml-component plugin by wrapping tree in posthtml() function, something like posthtml-modules is doing here https://github.com/posthtml/posthtml-modules/blob/master/index.js#L125 Or just set match and render function on tree before next process. I will check into in next days.