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

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

Closed
pReya opened this issue Feb 7, 2023 · 13 comments · Fixed by #32
Closed

Comments

@pReya
Copy link

pReya commented Feb 7, 2023

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
Copy link
Owner

delucis commented Apr 27, 2023

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
Copy link
Contributor

mayank99 commented Nov 22, 2023

@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
Copy link
Owner

delucis commented Nov 22, 2023

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 pReya changed the title Possibility to use single index file with multiple named exports without repeating/duplicating component names Possibility to use single index file (barrel file) w/o repeating/duplicating component names Nov 23, 2023
@pReya
Copy link
Author

pReya commented Nov 23, 2023

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
Copy link
Owner

delucis commented Nov 23, 2023

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
Copy link
Contributor

Are you interested in implementing this?

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

@mayank99
Copy link
Contributor

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
Copy link
Owner

delucis commented Nov 23, 2023

It’s actually stupider than it looks 😅

I think we’d need to change this function:

function formatNamedImports(
namedImport: NamedImportConfig[]
): [importString: string, exposures: string[]] {
const imports: string[] = [];
const exposedNames: string[] = [];
for (const imp of namedImport) {
if (typeof imp === 'string') {
imports.push(imp);
exposedNames.push(imp);
} else {
const [from, as] = imp;
imports.push(`${from} as ${as}`);
exposedNames.push(as);
}
}

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
Copy link
Owner

delucis commented Nov 23, 2023

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
Copy link
Contributor

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 withastro/compiler#899

@pReya
Copy link
Author

pReya commented Nov 24, 2023

@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
Copy link
Owner

delucis commented Nov 24, 2023

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
Copy link
Author

pReya commented Nov 24, 2023

Thanks for the detailed explanation!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants