withastro / roadmap

Ideas, suggestions, and formal RFC proposals for the Astro project.
320 stars 30 forks source link

Custom Client Directives #529

Closed matthewp closed 1 year ago

matthewp commented 1 year ago

Body

Summary

Provide an API for integrations to implement custom client: directives to provide greater control for when client-side JS is loaded and executed.

Background & Motivation

The last client directive added to core was the client:only directive in August 2021. Since that time the core team has been hesitant to add new client directives despite the community asking about them.

Allowing custom client directives would both:

Some examples of custom directives that people have wanted in the past:

Goals

Non-Goals

Future

Example

Usage

To use a custom directive in your app, add an integration like any other.

import { defineConfig } from 'astro/config';
import onClickDirective from '@matthewp/astro-click-directive';

export default defineConfig({
  integrations: [onClickDirective()]
});

And then use it in your Astro components:

---
import Counter from '../components/Counter.jsx';
---

<Counter client:click />

Implementation

An implementer of a custom client directive would do so through the hooks API:

export default function() {
  return {
    hooks: {
      'astro:config:setup': ({ addClientDirective }) => {
        addClientDirective({
          name: '@matthewp/astro-click-directive',
          key: 'click',
          entrypoint: '@matthewp/astro-click-directive/client.js'
        });
      },
    }
  }
}

The entrypoint being:

export default function(load, opts, element) {
  element.addEventListener('click', async () => {
    const hydrate = await load();
    await hydrate();
  }, { once: true });
}
spacedawwwg commented 1 year ago

@matthewp Would this change open up the possibility of "conditional" directives?

I'd like to see something similar to client:only="vue" but for ALL client directives accepting a boolean, with the help of props e.g:

---
import MyComponent from '../components/MyComponent.vue';
const { myProp } = Astro.props;
---

<MyComponent client:visible={myProp === 'A specific value that means it should hydrate'} />
louiss0 commented 1 year ago

Please do this I can then make a directive to solve one of the problems with Astro Right Now which is classes on the island and not the parent. See it in this issue.

matthewp commented 1 year ago

@spacedawwwg Yes, I think this would definitely enable that sort of thing. Whether we would add that option to the built-ins or not I'm not sure, probably needs a separate discussion (but it makes sense to me).

louiss0 commented 1 year ago

@spacedawwwg Yes, I think this would definitely enable that sort of thing. Whether we would add that option to the built-ins or not I'm not sure, probably needs a separate discussion (but it makes sense to me).

export default function(load, opts, element) {
element.addEventListener('click', async () => {
const hydrate = await load();
await hydrate();
}, { once: true });
}

I hope this is not the final design. I'd like to be able to export as many as I need from one file. being forced to use an entrypoint will require too much boilerplate code. A file per directive!

matthewp commented 1 year ago

cc @bluwy is there anything we can do to enable multiple directives per file?

We do have this same restriction in the renderer API and I haven't heard of it being a problem with anyone. But perhaps you might want to create a package of many small directives and it can be nice to export them all from an index module. Not a big deal imo though.

I could see a solution like in addClientDirective() taking the export name as an option, defaulting to default.

bluwy commented 1 year ago

Yeah we could add a new option for the names export, but we currently don't have this pattern for renderers, image services, etc. I don't anticipate many directives to be needed at once, or that boilerplate would be an issue (?), so I think it's fine to keep the scope down for now.

matthewp commented 1 year ago

Closing as this is completed and in stable.