delucis / astro-auto-import

Auto-import components in Astro projects
84 stars 2 forks source link

Possibility to use single index file (barrel file) w/o repeating/duplicating component names #13

Closed pReya closed 8 months ago

pReya commented 1 year ago

In my project I have a single index.ts file that just imports all of my separate components and then re-exports them as named exports (barrel file).

import ComponentA from "@components/ComponentA";
import ComponentB from "@components/ComponentB";

export {
  ComponentA,
  ComponentB
};

This way, I used to be able to use all my components through a single import statements (possibly with a TS alias), like this:

import { ComponentA, ComponentB } from "@components"

Now I want to use astro-auto-import, and I was wondering if there is any way to auto-import all the files that get exported from my index file, without repeating/duplicating all the module names, and without prefixing them with a namespace.

I'd love to have a single source of truth for all the usable components, and I'd love it to be my index file rather than the Astro config.

delucis commented 1 year ago

I think the way to go here might be to support import * as Components from '@components' syntax, so you could then use <Components.ComponentA /> etc. A bit more verbose but you could choose a short name like import * as UI ... and use <UI.ComponentA /> etc. I guess.

mayank99 commented 8 months ago

@delucis Have you given this any more thought? It would be nice to have a way to import namespaces. The syntax could look something like:

AutoImport({
  imports: [
    {
      // generates:
      // import * as Components from '@components/index.js';
      '@components/index.js': [['*', 'Components']],
    },
  ],
}),

The current workaround is to use a default export instead of a named export.

- export {
+ export default {
   ComponentA,
   ComponentB
 }

or use another file that re-exports the namespace object as a default export.

import * as Components from '@components/index.js';
export default Components;

The downside still is that the exports need to be done manually, i.e., the "barrel" file still needs to exist. It's verbose and can slow things down. But the solution to that is already tracked by a separate issue (#20).

delucis commented 8 months ago

Haven't had time to consider it much, but I like your syntax I think! I guess it's a little bit magic, but no more than the rest of the API design really.

Are you interested in implementing this?

pReya commented 8 months ago

The current workaround is to use a default export instead of a named export.

- export {
+ export default {
   ComponentA,
   ComponentB
 }

Thanks for the suggestion. How would you then load this file with astro-auto-import?

delucis commented 8 months ago

Thanks for the suggestion. How would you then load this file with astro-auto-import?

The usual way you’d do a default import:

AutoImport({
  imports: [
    {
      // generates:
      // import { default as Components } from '@components/index.js';
      '@components/index.js': [['default', 'Components']],
    },
  ],
}),

Then you can use them like:

<Components.ComponentA />
<Components.ComponentB />
mayank99 commented 8 months ago

Are you interested in implementing this?

Maybe! I was looking through the source code and couldn't quite understand how it works 😅

mayank99 commented 8 months ago

The usual way you’d do a default import:

AutoImport({
  imports: [
    {
      // generates:
      // import { default as Components } from '@components/index.js';
      '@components/index.js': [['default', 'Components']],
    },
  ],
}),

Then you can use them like:

<Components.ComponentA />
<Components.ComponentB />

Actually, it looks like this approach breaks hydration. Not sure if it's a bug in astro or astro-auto-import. See repro

delucis commented 8 months ago

It’s actually stupider than it looks 😅

I think we’d need to change this function:

https://github.com/delucis/astro-auto-import/blob/555c5e3c319b3232ca3b53e8704e61d1a05e7769/packages/astro-auto-import/src/index.ts#L46-L61

Right now that collects a bunch of x as y into an array, e.g. ['default as A', 'named as B'] then at the end wraps those in curly braces: { default as A, named as B }. Later that gets mixed into a line like import INSERT_HERE from 'module'.

So if we’re lucky, we can just special case '*' so it doesn’t end up in the curlies. One thing we might need to double check: can you combine namespace imports with named imports? i.e. is the following code valid:

import * as Namespace, { something } from 'module';

or would it have to be?

import * as Namespace from 'module';
import { something } from 'module';
delucis commented 8 months ago

Actually, it looks like this approach breaks hydration. Not sure if it's a bug in astro or astro-auto-import. See repro

Looks like an Astro bug. Even importing the barrel file directly breaks hydration:

import Barrel from '../components';
import Counter from '../components/Counter.tsx';

# Hello

<Barrel.Counter client:load />

<Counter client:load />
mayank99 commented 8 months ago

is the following code valid:

import * as Namespace, { something } from 'module';

or would it have to be?

import * as Namespace from 'module';
import { something } from 'module';

i think it needs to be the latter. in which case, maybe the below syntax would make more sense? this will prevent invalid imports.

AutoImport({
  imports: [
    {
      // generates:
      // import * as Namespace from 'module';
      'module': 'Namespace',
    },
  ],
}),

Looks like an Astro bug. Even importing the barrel file directly breaks hydration:

Opened https://github.com/withastro/compiler/issues/899

pReya commented 8 months ago

@delucis @mayank99 As far as I can see, withastro/astro#32 still only allows to use the imported components via namespace, e.g. Barrel.ComponentA, right? Any workaround to use it without the Barrel prefix?

delucis commented 8 months ago

Any workaround to use it without the Barrel prefix?

No. This would require astro-auto-import to know somehow what is exported from your index file and we can’t easily. To do that we’d need to read the index file, parse it with a JS AST tool, collect all the named exports and then use the result to build the import. It’s not impossible but it’s quite a lot more complex than the current implementation.

The prefix can be as short as you like, so if it’s just the authoring noise your concerned about, I’d recommend choosing something shorter like e.g. UI so you can refer to UI.ComponentA.

For now, if you need the unprefixed name, you’d need to use the named import approach and tell astro-auto-import yourself all the exports you have.

pReya commented 8 months ago

Thanks for the detailed explanation!