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

Single File addons #604

Closed
mehulkar opened this issue Mar 10, 2020 · 15 comments
Closed

Single File addons #604

mehulkar opened this issue Mar 10, 2020 · 15 comments

Comments

@mehulkar
Copy link
Contributor

This is pretty Pie in The Sky, so bear with me.

I want to be able to ship addons with a single index.js file and use named exports determine what should happen to the user's code. I think this would have several benefits:

  • Addon developers can determine their own file/directory structure
  • Easier to understand the size footprint of an addon
  • Learning curve would be so much smaller
  • No more inadvertent overwriting issues or namespace hogging (e.g. an addon that exports a services/whatever.js can't co-exist with an app that also does that)1.
  • The dependency tree for an addon could actually one day reflect what it exports (e.g. if it doesn't ship any components, it shouldn't need to install @emebr/component (or ember-source today)), it can just ship the default export with the methods that EmberCLI has today.

Before I get a 100 comments about why this wouldn't work, I know there are several technical challenges to making this happen, a few footguns that might be introduced, some overlap or toe-stepping on Embroider's work, and maybe some potential community fragmentation, but I don't think the current state of things is good either, so I'm opening this up.

Here's an example of what I mean:

// index.js
import FooComponent from './lib/components/foo';
import ManagerService from './lib/services/manager';
import Component from '@ember/component';

class BarComponent extends GlimmerComponent {};

export const components = [
  { name: 'myaddon-foo', klass: FooComponent,
  { name: 'myaddon-bar', klass: BarComponent
];

export const services = [
 { name: 'myaddon-manager', klass: ManagerService }
];

// this part is unchanged, except that I'm writing it in ESM
// instead of commonJS, but that's not the relevant part here.
export default {
  name: 'myaddon',
  included() {
    // whatever
  }
}

Footnotes

  1. namespacing is a thing and could prevent this
@Gaurav0
Copy link
Contributor

Gaurav0 commented Mar 21, 2020

I really don't know why anyone would want to configure their directory structure. Convention over configuration is a strength of Ember and that sort of configuration is what I come to Ember to avoid.

@NullVoxPopuli
Copy link
Sponsor Contributor

I really don't know why anyone would want to configure their directory structure.

Personally, I despise separating frontend code by "type" (routes folder, controllers folder, etc).
It should be grouped by feature, and this was a strong motivation of module unification and the ideals behind it.

What I would rather do is define my own conventions.
I imagine a project-layout-linter that:

  • knows what a route is
  • knows what a component is
  • etc
  • allows you to define patterns for where each type of thing is allowed to be located
  • throws lint errors when the patterns are violated.

Such a linter could be configured to describe the current project structure, as well as pods, as well as everything module unification was trying to do :)

@Gaurav0
Copy link
Contributor

Gaurav0 commented Mar 21, 2020

Personally, I despise separating frontend code by "type" (routes folder, controllers folder, etc).
It should be grouped by feature, and this was a strong motivation of module unification and the ideals behind it.

Not disagreeing. I just don't want to have to tell Ember and every addon and plugin where everything is. Which this proposal is proposing and module unification was not.

@mehulkar
Copy link
Contributor Author

The mindset I've been adopting with my RFCs/issues is progressive enhancement. If we can do something elegantly with more simplicity, we should enable that. In this particular case, there are two pain points:

  • it can often take several clicks in a GH ui to know where the meat of an addon is.
  • the addon and app directory problem really needs to be solved.

Both of these problems are solvable without changing anything and adding more documentation, but the rest of the JS ecosystem runs on module entry points, and I don't see why Ember's build pipeline cannot do the same.

I think it's pretty reasonable to say that if the blueprints can add the re-export into an app directory to "tell Ember where everything is", it can also add an export in the entrypoint of the npm module (index.js). The expanded for I wrote out in the original post here is pretty noisy, but it is an "unlocked" version of what's possible today.

I think that a conventional file structure is great, but in this case, because it's not built in the ecosystem's primitives, it feels more like imprisonment, rather than a happy path.

@NullVoxPopuli
Copy link
Sponsor Contributor

Very much agreed there. Especially as I'm desiring more and more tiny add-ons to help get around the lack of private 'stuff'. Like.. my ember-jsqr add-on shouldn't be exposing modifiers, yet it has to

@NullVoxPopuli
Copy link
Sponsor Contributor

Additionally, if I want to make a single test helper, an add-on feels really heavy for what is essentially a single function

@Gaurav0
Copy link
Contributor

Gaurav0 commented Mar 29, 2020

it can often take several clicks in a GH ui to know where the meat of an addon is.

In React, Vue, and Angular, you typically have to look through many more files and "more github clicks" to find anything. Do not compare an ember addon to a node.js library with a single entrypoint, it isn't. In these frameworks, there are configuration files that tell, for example, which route is associated with which component. There is often a "system", but that system changes with time and authors. Most of these plugins are a complete mess! Don't get me started with the apps.

the addon and app directory problem really needs to be solved.

Honestly, I've lived with this problem for more than six years now and no one has come up with a solution that allows the kind of flexibility that we need. Technically, having reexports in the app directory was initially developed as a best practice pattern that allowed an app maximum flexibility. Sticking everything in the app directory works but makes it difficult for an app to use parts of an addon while overriding other parts. Not reexporting and keeping everything in addon makes the app do configuration. I'm ready to live with both directories until someone actually does come up with a better solution. In the meantime, it's really easy to write a generator!

Very much agreed there. Especially as I'm desiring more and more tiny add-ons to help get around the lack of private 'stuff'. Like.. my ember-jsqr add-on shouldn't be exposing modifiers, yet it has to

Consider just using the addon/-private convention. Putting it in a directory named -private is a really good way to tell people they shouldn't need to depend on that code.

Additionally, if I want to make a single test helper, an add-on feels really heavy for what is essentially a single function

There is the copy and paste solution for very small bits of code.

I think that a conventional file structure is great, but in this case, because it's not built in the ecosystem's primitives, it feels more like imprisonment, rather than a happy path.

There are escape hatches when you need them. If you think the conventions are wrong, let's build better ones. But if you feel imprisoned by conventions, generally, whatever they are, then you are in disagreement with Ember's general philosophy.

@NullVoxPopuli
Copy link
Sponsor Contributor

Consider just using the addon/-private convention. Putting it in a directory named -private is a really good way to tell people they shouldn't need to depend on that code.

I already do. Also, modifiers can't be in folders :(

@mehulkar
Copy link
Contributor Author

mehulkar commented Jun 4, 2020

The core idea for addons is that discovery by file system is more or less the same as discovery by named exports. Plus discovery by filesystem means that we can't play nice with other bundlers or whatever that rely on entry points and exports.

@gossi
Copy link

gossi commented Jun 4, 2020

I want to share this issue with you, where I asked microsoft people from rushstack how to properly document ember addons: microsoft/rushstack#1864 Also he doesn't know about ember addons, his comments were super, super helpful.

An idea that since then sits in my head is a single file as entry to an addon. Kinda like the gateway into "your world of the addon". I think that pretty much resonates with the original idea in here. As tools are moving into this direction for the whole ecosystem it would be not very smart to knowingly stay outside of this.

Also I must agree with both @NullVoxPopuli (e.g. I don't like my folders structured by type (controllers, routes, model etc.) but rather by topic as this is more DDD-ish) and also with @Gaurav0 that it is a strength of ember you can hop on any new project and feel yourself comfortable. I share the fear that after this entry file there will be a lot of fragmentation.

I think all of this must be addressed and married together then I am +1000 on this.

I have an idea that hopefully will serve us as a discussion point for this. This is under the assumption we will also soon have single file components that we can export and import (as this helps up the thoughts) and at the same time borrows thoughts from the grand module unification efforts (ie. the good parts 😬)

  • There will be a single index.js file that describes the public API of that addon and will re-export from the addons internal structure.
  • This internal structure should be guided by conventions in the terms that it offers slots for stuff to put in - not completely type based - for me it really looks very similar to the proposed MU folder structure, that is to keep people jumping from project to project on focus
  • Oh yeah, ofc that lives in src/ 😂

@mehulkar
Copy link
Contributor Author

mehulkar commented Jun 4, 2020

That’s a great example. I’ve been saying for a while that addons should be consumable in the exact same way as any npm module. Entry points is a big part of that.

@gossi
Copy link

gossi commented Jun 15, 2020

I think from an authoring perspective this is +💯, to have a single file where you expose your public API. From a consumers perspective, I see this is not pairing well with the strong conventions ember provides. Maybe some examples to explain that:

import { MySuperDopeComponent } from 'my-addon';

// vs

import MySuperDopeComponent from 'my-addon/components/super-dope';

For somebody - like me - who spent a long time isolated in ember context, the last import statement is very well understood and exports like this are the normality as it follows the strong conventions ember put up. Also given my history in php and java, this very much matches the situation over there.

Now from js perspective, the latter sounds weird, as the trend is going to import from that single entry file into your lib.

If I would need to choose I'd go with the latter example as per my personal preference but I'd also emphasize on the idea behind the single entry file here. Basically I wanna have the best of both 🙈

It's more like sharing my personal parts on that but hope that give some more explanations.

@mehulkar
Copy link
Contributor Author

For somebody - like me - who spent a long time isolated in ember context, the last import statement is very well understood

I get what you're saying, but for what it's worth, if ember addons behaved more closely to normal modules, the import statement would be:

import MySuperDopeComponent from 'my-addon/addon/components/super-dope`

In other words, a "single entry point" is just for convention's sake. You can still (always) reach into the internals file paths of the module and grab what you want.

The big point here is that we want a strong convention for exports, but I don't want it to be declared with my filesystem. The index.js entrypoint can be the alternative, without giving up anything.

@wagenet
Copy link
Member

wagenet commented Jul 23, 2022

I'm closing this due to inactivity. This doesn't mean that the idea presented here is invalid, but that, unfortunately, nobody has taken the effort to spearhead it and bring it to completion. Please feel free to advocate for it if you believe that this is still worth pursuing. Thanks!

@wagenet wagenet closed this as completed Jul 23, 2022
@NullVoxPopuli
Copy link
Sponsor Contributor

to follow up, this issue's main prompt:

I want to ship everything in an addon in one file

is possible with the v2 addon format, and some clever config in the rollup.

and with <template> / stsrict mode, you don't even need the clever config.

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

No branches or pull requests

5 participants