#How to build an integration which itself requires integrations and exports a component?

21 messages · Page 1 of 1 (latest)

surreal crypt
#

I have an Asrto + React portfolio site. One of the pages on my site is a GPT chat app built with React, styled with Tailwind, using the Vercel AI-SDK under the hood. I want to separate this from my portfolio app and the best way I have found is to make an integration, but I have not seen any integrations that rely on other integrations and that export a component.

The chat app portion consists of my ChatUI components, exported as a single combined component, and an api route in pages/api/chat.ts

  1. How do I handle it's required integrations? Do I rely on the host projects, or is there a way for my integration to add them if not available in a host project, and use them from the host project if they are available?

  2. Does this also mean that my integration will have to come after react and tailwind if the host projects already contains them? What if the host project wants to add them after already adding my integration?

  3. How do I build/bundle my integration? I am using this template https://github.com/morinokami/astro-integration-template which includes TSUP, is this what I use to build, instead of astro build?

  4. The ChatUI uses shadcn components so Radix and several others as dependencies and it contains its own css variables and tailwind config, how is this handled in a project that may already have it's own TW config and CSS? I guess the CSS the integration uses will be bundled with it and there won't be any conflicts?

  5. How do I handle the plethora of dependencies the integration needs? Some will be in most astro projects, while others are less likely to be. I'd like to avoid duplication if possible, and use the host projects deps when available, while still being able to work in a project without any of the integrations required dependencies.

Any help would be seriously appreciated, there are very few resources for building integrations and I haven't found any examples that export tailwind styled react components with lots of dependencies!

#

This is the integration pseudo code I came up with so far:

import type { AstroIntegration } from 'astro';

export default function createChatIntegration(): AstroIntegration {
  return {
    name: 'astro-chat-integration',
    hooks: {
      'astro:config:setup': ({ injectRoute }) => {
        injectRoute({
          pattern: '/pages/api/chat',
          entrypoint: 'astro-chat-integration/pages/api/chat.ts'
        });
      },
      'astro:config:done': ({ config }) => {
        if (config.output !== 'hybrid' && config.output !== 'server') {
          console.warn('Warning: The chat integration requires hybrid or server output mode for the API route to function correctly. Please update your Astro config.');
        }
        console.log('Chat integration: API route injected successfully. Remember to set up hybrid rendering in your Astro config for the API route to work correctly.');
      },
    },
  };
}

export { default as ChatUI } from './components/ChatUI';
magic jacinth
surreal crypt
magic jacinth
#

How do I handle it's required integrations? Do I rely on the host projects, or is there a way for my integration to add them if not available in a host project, and use them from the host project if they are available?
https://astro-integration-kit.netlify.app/utilities/add-integration/ with the ensureUnique prop

Does this also mean that my integration will have to come after react and tailwind if the host projects already contains them? What if the host project wants to add them after already adding my integration?
I think that should be fine, @quasi sphinx can you confirm?

the template
ofc I'd recommend mine houston_sweat_smile https://github.com/florian-lefebvre/astro-integration-template

How do I handle the plethora of dependencies the integration needs? Some will be in most astro projects, while others are less likely to be. I'd like to avoid duplication if possible, and use the host projects deps when available, while still being able to work in a project without any of the integrations required dependencies.
This is not that easy. I think you should not mind too much. If you want the user to control the version, go for peer deps

#

Also we have an #integrations channel if you want to ask stuff, probably easier as we have a few great integration builders

surreal crypt
# magic jacinth > How do I handle it's required integrations? Do I rely on the host projects, or...

Thanks! addIntegration looks helpful, but I'm a little confused-if I understand correctly ensureUnique adds the integration's integration even if the host project already has it? And the default behavior of addIntegration is to use the host project's integration and add it if not available in host? In this case I think I'd want to leave ensureUnique enabled to avoid duplicating dependencies unless I want it locked to a specific version, is that correct?

Can you elaborate a bit on dependency management?
I think I'd avoid using peer deps for most deps except astro and maybe react and tailwind, but I'd want the integration to be self contained enough to be able to work even if the host project doesn't have any required deps; so I guess I'd list them all in deps, but if I do, would that add duplicate dependencies when a host project already includes them?

Or would specifying ranges like "react": "^17.0.0 || ^18.0.0" enable the integration to use a host projects deps if available?

Sorry to bombard you with questions! I appreciate the help you've provided so far, I think I'm on the right track! And I'll definitely check out the integrations channel, thanks!

quasi sphinx
#

Does this also mean that my integration will have to come after react and tailwind if the host projects already contains them? What if the host project wants to add them after already adding my integration?
Unless you are relying on some very internal detail that requires your integration to run in a particular order it should be fine. I've only seen two integrations that cause this across the ecosystem.

Thanks! addIntegration looks helpful, but I'm a little confused-if I understand correctly ensureUnique adds the integration's integration even if the host project already has it? And the default behavior of addIntegration is to use the host project's integration and add it if not available in host? In this case I think I'd want to leave ensureUnique enabled to avoid duplicating dependencies unless I want it locked to a specific version, is that correct?

For the UI framework integrations using addIntegration should suffice since they don't take any options. If you also rely on integrations that require you to pass options to it you'd need more advanced management (like my Modular Station).

Can you elaborate a bit on dependency management?
I think I'd avoid using peer deps for most deps except astro and maybe react and tailwind, but I'd want the integration to be self contained enough to be able to work even if the host project doesn't have any required deps; so I guess I'd list them all in deps, but if I do, would that add duplicate dependencies when a host project already includes them?
If your project depends on, for example, react: ^17 and the project using your integration depends on react: ^16, then two versions of React would be installed since, as far as their version states, those two are incompatible with each other.

Or would specifying ranges like "react": "^17.0.0 || ^18.0.0" enable the integration to use a host projects deps if available?
If your components are compatible with multiple versions of React, you can define your dependencies in that way. But be aware that it doesn't guarantee there won't be a duplicate installation. You can't guarantee that since you might get a duplicate due to another dependency of the project using your integration having an incompatible range. There are ways to detect this, and maybe this is worth an article, but mostly, this is a responsibility of the project and not of the dependency. The most you'd be able to do is break the build telling that something is wrong and the user has to figure it out.

surreal crypt
#

This is very helpful, thank you! Just one more question:

For the UI framework integrations using addIntegration should suffice since they don't take any options. If you also rely on integrations that require you to pass options to it you'd need more advanced management (like my Modular Station).

In my portfolio project I'm using the tailwind integration with the applyBaseStyles option set to false, this isn't needed in and doesn't apply to the code I want to use in the integration. Does addIntegration account for this, would it then add a second copy of the integration without that option set? Is that solved by ensureUnique?

quasi sphinx
#

ensureUnique won't add the integration if it is already present, regardless of which options were passed in

surreal crypt
# quasi sphinx `ensureUnique` won't add the integration if it is already present, regardless of...

Got it, so if i understand correctly, even when setting ensureUnique to false, addIntegration won't add the integration if present in host project despite the options being different, addIntegration doesn't account for integration options at all.

So in my scenario, addIntegration will use the tailwind integration with the options set in the host project, I think this is fine actually because this is naturally the host project's responsibility.

Do you know of any examples of integrations which implement patterns similar to this? Using other integrations within an integration and relying on dependencies that may or may not be present in the host project?

quasi sphinx
#

Got it, so if i understand correctly, even when setting ensureUnique to false, addIntegration won't add the integration if present in host project despite the options being different
No, if ensureUnique is false, the integration will be duplicated. If it is true, then whatever you passed in will be ignored in favor of the integration that was already installed

surreal crypt
quasi sphinx
#

In that case it will be added with the options you gave, even if it is a duplicate.

An example to avoid confusion. Imagine this configuration:

defineConfig({
  integrations: [
    integrationA({value: 'from config'}),
    integrationB({value: 'from config'}),
    yourIntegration(),
  ],
});

Then on your integration you do this:

{
  hooks: {
    'astro:config:setup': params => {
      addIntegration(params, {
        integration: integrationA({value: 'from your integration'}),
        ensureUnique: false,
      });
      if (!hasIntegration(params, {name: 'integrationB'})) {
        addIntegration(params, {
          integration: integrationB({value: 'from your integration'}),
          ensureUnique: true,
        });
      }
    },
  },
}

The first call to addIntegration would inject the duplicate since ensureUnique is false.
The second call would fail since ensureUnique is true and the integration is already installed.

The overall result ignoring the conflict would be equivalent to this configuration:

defineConfig({
  integrations: [
    integrationA({value: 'from config'}),
    integrationB({value: 'from config'}),
    yourIntegration(),
    integrationA({value: 'from your integration'}),
  ],
});
#

I said before that it would ignore what you passed if it is already installed. That was incorrect, sorry about that.

#

You need to check with hasIntegration

surreal crypt
# quasi sphinx In that case it will be added with the options you gave, even if it is a duplica...

Thanks, I think I understand now. So in any case where I'd want to ensure that my integration has access to an integration with specified options, ignoring options set in the host projects config, I would want to set ensureUnique to false?

Does this actually work in practice, when theres a duplicate integration injected by another integration with different options;
Do the duplicated integrations conflict with the rest of the host project, or is the injected integration and its options scoped to the integration that injected it?

quasi sphinx
# surreal crypt Thanks, I think I understand now. So in any case where I'd want to ensure that m...

Thanks, I think I understand now. So in any case where I'd want to ensure that my integration has access to an integration with specified options, ignoring options set in the host projects config, I would want to set ensureUnique to false?
Correct

Does this actually work in practice, when theres a duplicate integration injected by another integration with different options;
This is highly dependent on the integration. Most integrations DON'T behave correctly when duplicated.

Do the duplicated integrations conflict with the rest of the host project, or is the injected integration and its options scoped to the integration that injected it?
Yes, they will conflict. Both will run for the entire project. The integration may handle that by itself (I do that on mine, most people don't cause it is kinda of a mess)

surreal crypt
# quasi sphinx > Thanks, I think I understand now. So in any case where I'd want to ensure that...

I see, so it's best to avoid this unless absolutely necessary. Fortunately in my case I think I can leave it up to the project to handle this particular option, importing the component in my base layout takes care of applying the tailwind styles in accordance with my host projects options, and will also work normally when the option is not passed in the host project.

One thing I'm confused about, you mentioned in the example that hasIntegration is needed to check, but doesn't addIntegration already use hasIntegration to perform this check? What is the need for the additional check, was that just for the example?

With ensureUnique: true, addIntegration, by default, doesn't add an integration when that integration is already detected in the project, right?

magic jacinth
#

Thanks Luiz for handling this thread, I was not available houston_salute

quasi sphinx
#

but doesn't addIntegration already use hasIntegration to perform this check?
It does, but it doesn't ignore your integration if it is present, it throws.