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

Bower installing dependencies as siblings instead of nesting? #157

Closed
robdodson opened this Issue Nov 25, 2012 · 50 comments

Comments

Projects
None yet
@robdodson
Copy link

robdodson commented Nov 25, 2012

I think perhaps I'm using bower incorrectly and assuming it works similar to node_modules.

Basically Project A has a component.json file which installs everything to its components directory. So far so good.

Next I have Project B which depends on Project A. I assumed it would install Project A's dependencies inside a components dir inside of Project A. But it seems to install Project A's dependencies as siblings of Project A.

What I was expecting:

Project B/
    components/
        Project A/
            components/
                ... a bunch of components ...

What I get:

Project B/
    components/
    Project A
    ... Project A's components ...

On the one hand this obviously makes sense. You might say that some jquery plugin depends on jquery so they're installed as siblings. On the other hand, Project A expects its dependencies to be inside its components dir so this structure doesn't work...

Any ideas?

@satazor

This comment has been minimized.

Copy link
Member

satazor commented Nov 25, 2012

That doesn't make sense in the browser world simply because modules aren't self contained. What I'm trying to say is that most deps can't coexist at the same time. Take jquery for instance, if we allow both 1.7.1 and 1.8.2, one of them would overwrite each other.

@satazor satazor closed this Nov 25, 2012

@robdodson

This comment has been minimized.

Copy link
Author

robdodson commented Nov 25, 2012

So is it possible for a project using bower to be consumed by another project using bower?

In my case Project A is a Bootstrap theme which uses Bower to pull down Bootstrap core. Its LESS files import components/bootstrap/less.

For instance:

@import "../../components/bootstrap/less/variables.less";

Now in Project B I want to use this bootstrap theme. So I add it to my bower file. But since it can no longer find bootstrap the LESS files blow up.

@satazor

This comment has been minimized.

Copy link
Member

satazor commented Nov 26, 2012

hmmm I now understand the problem.
The issue is that you are providing a theme that is not built yet (does not contain a single .css file with the precompiled stuff). This makes sense in some situations.

What bower could do is replicate a structure similar to npm but with some changes:

  • The root components directory should still contain all the flatten deps
  • All deps inside nested components should be a symbolic link pointing to the respective root components dep

e.g:

Project B/
    components/
        Project A/
            components/
               Project C (symlink to root Project C)
        Project C

@satazor satazor reopened this Nov 26, 2012

@robdodson

This comment has been minimized.

Copy link
Author

robdodson commented Nov 26, 2012

That's an interesting solution.

I think for now my best bet is going to be to build the theme, make that part of the repo and encourage my users to just use that. The built folder will pull in all the LESS files it needs.

edit

It might be nice, if I'm going to go the route of encouraging people to just use my build folder, to have the option to only download that folder when someone runs bower install. I guess bower-installer does this perhaps(?) but it could be useful as a built in option.

@satazor

This comment has been minimized.

Copy link
Member

satazor commented Nov 26, 2012

@fat @sindresorhus what's your toughts on this?

@robdodson

This comment has been minimized.

Copy link
Author

robdodson commented Nov 26, 2012

at least having a devDependencies option would help. That way all of Project A's stuff won't end up in Project B.

edit

I see this is already in the works :D

#80

@cobbweb

This comment has been minimized.

Copy link

cobbweb commented Nov 28, 2012

I have this problem too with different components that import LESS stylesheets from each other. For now after I run bower install I have to cd into components/my-theme and run bower install again so it installs Bootstrap at the right location.

@robdodson

This comment has been minimized.

Copy link
Author

robdodson commented Nov 29, 2012

I wonder if you could use npm or grunt to execute those cd and bower install commands? Maybe the whole thing could be a big grunt or npm task of some kind? Not ideal but might work in the short term...

@cobbweb

This comment has been minimized.

Copy link

cobbweb commented Nov 29, 2012

Yea I could automate quite easily from a grunt file. But I was hoping for a solution to be added into bower. @satazor's idea about nesting symlink components up the root level components is great.

Personally, I was thinking a different configuration field for those dependencies:

// my-theme/compontent.json
{
  "name": "my-theme",
  "directDependencies": {
    "bootstrap": "2.0.1"
  }
}

// my-app/compontent.json
{
  "name": "my-app",
  "dependencies": {
    "my-theme": "git://github.com/me/my-theme.git",
    "bootstrap": "2.2.1"
  }
}

Then in my-app when you run bower install you end up with:

  • my-app/components/my-theme
  • my-app/components/bootstrap
  • my-app/components/my-theme/components/bootstrap

I think an issue with symlinking could arise based how I've configured above. my-app needs Bootstrap v2.2.1 but my-theme needs Bootstrap v2.0.1, so which version ends in my-app/components/bootstrap?

@satazor

This comment has been minimized.

Copy link
Member

satazor commented Nov 29, 2012

@cobbweb for each dependency, bower will find the most suitable one for the whole project (the highest that match all the packages semver ranges). In your case, if one app needs ~2.2.1 and a package needs ~2.0.1, bower will emit an error saying its impossible to resolve bootstrap dependency.

This approach is the better one for the web world, because as I said above, deps are not self contained and would overwrite each other or add duplicate code to the final application.

@satazor

This comment has been minimized.

Copy link
Member

satazor commented Nov 29, 2012

@paulirish @addyosmani @sindresorhus can you leave your thoughts on the symlink solution?

@necolas

This comment has been minimized.

Copy link
Contributor

necolas commented Nov 29, 2012

@robdodson

This comment has been minimized.

Copy link
Author

robdodson commented Dec 2, 2012

if it's scary to have the symlink solution on by default perhaps it could be an advanced option?

@satazor

This comment has been minimized.

Copy link
Member

satazor commented Dec 2, 2012

I see no harm in doing symlinks, and this would be kind of easy to implement.

@sindresorhus

This comment has been minimized.

Copy link
Member

sindresorhus commented Jan 14, 2013

I think the symlink solution is the way to go.

@robdodson

This comment has been minimized.

Copy link
Author

robdodson commented Jan 14, 2013

Just an FYI, for anyone using Sublime symlinks display in a rather buggy fashion.

http://sublimetext.userecho.com/topic/89047-symlinked-folders-arent-displayed-properly-in-the-sidebar/

@davidpfahler

This comment has been minimized.

Copy link

davidpfahler commented Jan 15, 2013

From what I recently wrote down about our problems with using bower:
"When you split a project into multiple components but the main project is still one repository, you can predict the components in this repository will be in ./components/. But when you use this main repository in another project, the dependencies of main project will be installed next to it, not inside its folder."

I have another (but not exclusive) idea on how to fix this. I think having nested dependencies actually does make sense in the browser (sometimes). So having it on (by default or choice) could make sense in development. But then, bower needs a build step, where it looks for multiple occurrences of the same dependency with different versions and prompts the developer which version should be used. I think this is better than automatically choosing a version. When the decision is made, it then merges into a flat directory structure (how it is now). That being said, I really would love to see a symlink solution soon.

Thoughts?

@satazor

This comment has been minimized.

Copy link
Member

satazor commented Jan 31, 2013

@davidpfahler Interesting. Still I can spot somes issues with that approach.
While developing, versions of packages might not be coincident to the ones used while building. This can cause subtle bugs that weren't spotted in dev.
Another issue is that in dev, multiple versions of different libraries simply can't co-exist together (e.g.: they do window.myLibrary = ...).

@satazor

This comment has been minimized.

Copy link
Member

satazor commented Feb 18, 2013

The symlink solution would need administrator permissions on windows :(

@cobbweb

This comment has been minimized.

Copy link

cobbweb commented Feb 18, 2013

@satazor Based on my previous example, my-app wouldn't (and shouldn't) directly consume the directDependecies listed in my-theme. This wouldn't overwrite any code or deliver duplicate code to the client.

@sindresorhus

This comment has been minimized.

Copy link
Member

sindresorhus commented Mar 15, 2013

@satazor is there any way we can just ignore Windows an implement this anyway? I know npm did with npm link.

@necolas

This comment has been minimized.

Copy link
Contributor

necolas commented May 1, 2013

I've been needing a solution to this for CSS imports too. Shame to hear about the problem with Windows.

I wonder if there's another solution. Perhaps rather than using symlinks for end consumers, we could use symlinks to simulate peerDependencies during package authoring, thereby ensuring that the paths in the package make sense when installed in an app?

@addyosmani

This comment has been minimized.

Copy link
Member

addyosmani commented May 1, 2013

I think the symlink solution is appropriate however I feel wary with this causing issues for Windows users :/. Interested in the proposal @necolas suggested above.

@necolas

This comment has been minimized.

Copy link
Contributor

necolas commented May 4, 2013

My idea isn't possible, afaict. So, nevermind!

@necolas

This comment has been minimized.

Copy link
Contributor

necolas commented May 8, 2013

BTW, the use case for CSS is very tricky. Even if you could get all the paths to work both during package authoring and consumption, you could end up with multiple files (in different components) @import-ing the same shared component dependency. You'd need a build step that worked out the dep tree for the CSS files. Not trivial stuff, so don't think Bower core can solve this alone.

@desandro

This comment has been minimized.

Copy link
Member

desandro commented May 8, 2013

Yes, I was thinking the same thing. Is this issue only related to CSS (and CSS pre-compilers)? If so, we should provide workflows that can address the CSS imports issue, rather than faking nested dependencies.

@robdodson

This comment has been minimized.

Copy link
Author

robdodson commented May 8, 2013

In my experience the pain is primarily around css. We've worked around it by assuming things are always going to be installed as siblings.

@necolas

This comment has been minimized.

Copy link
Contributor

necolas commented May 8, 2013

Yep. In terms of working with dozens of CSS components, I think the problems related to that are likely to be best served by adapters and/or other tools. This whole space is still in its infancy, and people are working out how to wire everything up, so it will be interesting to see what kind of solutions pop up.

@itay

This comment has been minimized.

Copy link

itay commented Aug 21, 2013

This isn't just for CSS. I'll give another example which we've run into that
is somewhat of a showstopper for us.

Consider the case of having three moduules, module-A, module-B and module-C, which have dependencies
on each other (defined below), and all use require.

module-C:
    c.js:
        define(function(require, exports, module) {
            return { message: "this is C!" };
        });
    bower.json:
        {
            name: "module-C",
            dependencies: {}
        }
module-B:
    b.js:
        define(function(require, exports, module) {
            var C = require("./components/module-C/c");
            console.log("C's message: " + C.message)
            return { message: "this is B!" };
        });
    bower.json:
        {
            name: "module-B",
            dependencies: {
                "module-C": "1.0.0"
            }
        }
module-A:
    a.js:
        define(function(require, exports, module) {
            var B = require("./components/module-B/b");
            console.log("B's message: " + B.message);
            return { message: "this is A!" };
        });
    bower.json:
        {
            name: "module-A",
            dependencies: {
                "module-B": "1.0.0"
            }
        }

Now, while developing module-B, it will look like this in the filesystem:

module-B/
    b.js
    components/
        module-C/
            c.js

However, if I now install it while developing A, it will look like this:

module-A/
    a.js
    components/
        module-B/
            b.js
        module-C/
            c.js

Finally, if I take module-A and install it in my app, it will look like this:

myapp/
    myapp.js
    components/
        module-A/
            a.js
        module-B/
            b.js
        module-C/
            c.js

As you can see, the paths I use to require things with are now all wrong, since
things get installed side-by-side rather than nested. Side-by-side also means
I can't get version independence, even though require allows me to do so.

I understand that not every one wants nested installation, because if you aren't
using require.js, it might not be the best option for you. But it would be good
to at least have it as an option, and ideally supported on all platforms (which
symlinks would not really work).

@satazor

This comment has been minimized.

Copy link
Member

satazor commented Aug 21, 2013

@itay dependencies should never require other dependencies with relative paths, but with aliases. Checkout this example: https://github.com/IndigoUnited/address#how-to-use. As you see, I always require dependencies by their name, not relatively. This is the standard way to do this kind of stuff in AMD.

@itay

This comment has been minimized.

Copy link

itay commented Aug 21, 2013

@satazor: I actually don't agree. Using names as defined in 'paths' just introduces another global "registry" for which there is no reason, at least in the cases I have seen. While it is convenient to name "components/backbone/backbone.js" as "backbone" and require it like that, it now means that if you want to have two versions of Backbone, you're out of luck, because only one of them can be the global one.

Finally, if you depend on paths, it means that the end-user has to add all this information to the paths definition. If you use relative paths, it can be self contained and the user doesn't need to do anything except require them.

We've used this pattern to allow people to build completely contained components that don't require the end-user of these components to need to add anything to their require.config. They just require their end result. So, for example, if they want to require our "parallel coordinates" component (which depends on d3), they don't need to do anything except require("./path/to/parcoords"), which will require d3 relatively itself, without needing the user to add anything to the config.

Finally, I don't mean to say that doing things by adding it to 'paths' or asking developers to do that is the wrong thing. It's just not the way I prefer to go, and I don't think doing relative requires is wrong either. My only comment is that Bower shouldn't make me have to do one or the other - it should be agnostic. However, right now, due to the side-by-side installation, I'm essentially forced into the 'global' definition.

@danielchatfield

This comment has been minimized.

Copy link

danielchatfield commented Aug 21, 2013

2 versions of backbone

That is exactly why the dependencies aren't nested - that isn't something
that should be happening in browser-land.

On 21 August 2013 19:43, Itay Neeman notifications@github.com wrote:

@satazor https://github.com/satazor: I actually don't agree. Using
names as defined in 'paths' just introduces another global "registry" for
which there is no reason, at least in the cases I have seen. While it is
convenient to name "components/backbone/backbone.js" as "backbone" and
require it like that, it now means that if you want to have two versions
of Backbone, you're out of luck, because only one of them can be the global
one.

Finally, if you depend on paths, it means that the end-user has to add all
this information to the path. If you use relative paths, it can be self
contained and the user doesn't need to do anything except require them.


Reply to this email directly or view it on GitHubhttps://github.com//issues/157#issuecomment-23039939
.

For contact info visit: www.danielchatfield.com

@danielchatfield

This comment has been minimized.

Copy link

danielchatfield commented Aug 21, 2013

That paths thing is usually automated by a tool (bower exposes all the
relevant information).

On 21 August 2013 19:49, Daniel Chatfield chatfielddaniel@gmail.com wrote:

2 versions of backbone

That is exactly why the dependencies aren't nested - that isn't something
that should be happening in browser-land.

On 21 August 2013 19:43, Itay Neeman notifications@github.com wrote:

@satazor https://github.com/satazor: I actually don't agree. Using
names as defined in 'paths' just introduces another global "registry" for
which there is no reason, at least in the cases I have seen. While it is
convenient to name "components/backbone/backbone.js" as "backbone" and
require it like that, it now means that if you want to have two versions
of Backbone, you're out of luck, because only one of them can be the global
one.

Finally, if you depend on paths, it means that the end-user has to add
all this information to the path. If you use relative paths, it can be self
contained and the user doesn't need to do anything except require them.


Reply to this email directly or view it on GitHubhttps://github.com//issues/157#issuecomment-23039939
.

For contact info visit: www.danielchatfield.com

For contact info visit: www.danielchatfield.com

@satazor

This comment has been minimized.

Copy link
Member

satazor commented Aug 21, 2013

@itay using relative paths decreases the flexibility of users consuming/using your library. For instance, what if I wanted to use a library of yours but put it in vendor instead of components? I would have to use map and override all your paths to deps for it to work. This is painful.

@itay

This comment has been minimized.

Copy link

itay commented Aug 21, 2013

Thanks for taking the time to respond, I'll try and consolidate my thoughts:

2 versions of Backbone: I strongly disagree. With proper encapsulation (a la AMD), there is no reason this can't happen. For example, there is no reason that I can't have one thing is d3.v3 and another use d3.v2 - they are not mutually exclusive. Same with jQuery and Backbone (with either noConflict and/or AMD). Finally, I think that the web will move more and more to this direction (e.g. modules in ES6), and I hope that bower can be a part of that space too.

Path automation: yes, it's possible, but it's another step to force people to go through. My ideal situation is that after installation, I'm ready to go, and that I don't need to run another build step. Even if I make install/build the same thing, it just seems unfortunate. I understand that some people may want it, but I don't think everyone does.
Relative paths: That's a very fair complaint, and one I hadn't really thought about. My first reaction is that a component should control what gets installed underneath it. So in my above example, moduleB would specify that things should always get installed in components, so that it gets predictability. In that case, the only thing that would be affected by vendor would be the top level usage (myapp) in my example, which would have to specify it. That way you can make modules be installed in a way that they can predict ahead of time. Another option might be to 'templatize' the directory, so in the code it might require('./{{components_dir}}/moduleC/c'), but I'm less a fan of that.

In a more general sense, I hope bower can be flexible enough to support these different usages. Personally I think the 'npm'-style of nested dependencies with version independence is the way to go, but I understand others might not want that for the browser.

Overall, I do want to say that I think bower is awesome, and it's so close to what I want. It's generally very non-prescriptive, except in this one particular case, which happens to be important to me. I say that because I don't want my feedback to make it seem like I don't appreciate the work you guys have done and also that I recognize how great it is already.

Quick addition: I do also realize that a perfectly reasonable response might be "bower is not for you". I hope that isn't the case though, because I'd really like to use it, and it seems (given the volume of this thread) that others do see some value in having nested dependencies.

@danielchatfield

This comment has been minimized.

Copy link

danielchatfield commented Aug 21, 2013

@itay I cannot think of a single reason why you would want 2 versions of jQuery. This is the web! Distributing multiple versions of the same library is bonkers.

In a pre-bower world if you wanted to use 2 jQuery plugins that worked with different versions of jQuery then you either had to decide watch one was more important, persuade the author to update it or even better update it yourself. jQuery.noConflict() is for stopping jQuery from conflicting with other libraries, not for including multiple jQuery versions.

Before long your website will have a horrifically large js overhead.

There is an argument to use symlinks to simulate the npm way of doing things (but it would be just that - a simulation, the deps would still be flat - no 2 versions of jQuery!)

@danielchatfield

This comment has been minimized.

Copy link

danielchatfield commented Aug 21, 2013

The nested dependencies work for node because file space isn't an issue and there is almost no overhead involved with loading 2 different versions of a module instead of 1.

The web is fundamentally different in that everything has to travel over the internet which is a massive bottleneck.

Sure, this slows down development when compared to node modules as you have to ensure that all components agree on common dependencies but that is not something that can be solved by nested dependencies - it may make the developers job easier but at great expense to the end user.

@itay

This comment has been minimized.

Copy link

itay commented Aug 21, 2013

I guess I fundamentally disagree on using two versions being bad vs. good.

I agree that there is a cost in terms of file size, but sometimes that is both unavoidable and sometimes desired. Consider for example this case (I'll use d3 as an example again). Say I have two reusable modules I want to use: nv.d3 (a charting library based on d3) and sankey (a d3 "plugin"). 'nv.d3' requires d3 2.x, while sankey requires d3 3.x.

As a developer who wants to use these reusable modules using bower, I actually can't. It'll error out and say "can't resolve conflict", because there is no way to match versions. This is even if I want to include them on different pages. So, at this point, I have two options:

  1. Give up, and only include one of them.
  2. Do a completely separate bower install in completely separate directories and install one in each.
  3. Fork these modules and modify them to have the same dependency.

Neither of these is really ideal, and as a developer, I don't want to deal with any of this. I want to use these things, I shouldn't care about their internal dependencies.

If I decide to care because of file size, that's fine, but that is up to me. I'm all for bower facilitating this and even showing how you can combine multiple dependencies into one (and even doing this using symlinks if specified), but it should not be the default.

The benefit of reusable components is that as a developer I can just install them. If bower requires me to then go ahead and modify those components if I want to use them, that makes the barrier for entry much higher. I actually ran into this in my first use of bower: I used the yeoman webapp generator, and then tried to install backbone but couldn't, because backbone depends on jquery 2.0.x while the default installation of the generator requires jquery 1.9.x.

Trying to save on disk space/bandwidth when loading the site is a commendable goal, and it's great that bower wants to assist in that. But that is an optimization that should be up to the developer to take advantage of. Not allowing multiple versions essentially excludes you from using particular components together unless you manually modify them, which seems to me more like a correctness issue.

Finally, note that even require.js has ways to allow you to support multiple versions of the same library: http://requirejs.org/docs/api.html#config-map. I personally dislike the solution (because it forces a developer to centralize all the knowledge for every component and their dependencies in a single require.config, and it again uses global names), but it is explicitly supported.

Anyway, sorry for typing so much, and I appreciate you taking the time to explain your point of view :)

@leog

This comment has been minimized.

Copy link

leog commented Sep 20, 2013

I'm in a similar situation @itay described. I'm developing an application that gathers and manages components. Those components have a hierarchy, there are apps, those apps have pages and pages have widgets. Each of those components can be developed by different teams, hence, each of the components have their own dependencies and the root application should build them and expose the app components to the end user.

I was using Bower in a different (simpler) project with a flat structure and I loved it. I would described it as love at first use. But I'm now wishing that Bower can be used as modules are handled on Node but thoughtfully and carefully as there would be some concerns and problems to solve. IMHO, as a NodeJS beginner developer, Senior Web UI Developer and Software Engineer, reusable and modular functionality is the way to go, Internet should evolve that way and having Bower support it will be a very good start.

@kaheglar

This comment has been minimized.

Copy link

kaheglar commented Sep 24, 2013

@italy In your problem example is there a reason you can't do this?

module-C:
    c.js:
        define(function(require, exports, module) {
            return { message: "this is C!" };
        });
    bower.json:
        {
            name: "module-C",
            dependencies: {}
        }
module-B:
    b.js:
        define(function(require, exports, module) {
            var C = require("components/module-C/c");
            console.log("C's message: " + C.message)
            return { message: "this is B!" };
        });
    bower.json:
        {
            name: "module-B",
            dependencies: {
                "module-C": "1.0.0"
            }
        }
module-A:
    a.js:
        define(function(require, exports, module) {
            var B = require("components/module-B/b");
            console.log("B's message: " + B.message);
            return { message: "this is A!" };
        });
    bower.json:
        {
            name: "module-A",
            dependencies: {
                "module-B": "1.0.0"
            }
        }
@kaheglar

This comment has been minimized.

Copy link

kaheglar commented Sep 24, 2013

@robdodson Would specifying all import paths relative to project root "." not solve this problem?

@import "components/bootstrap/less/variables.less";
@kaheglar

This comment has been minimized.

Copy link

kaheglar commented Sep 24, 2013

One observation I would make is that the path/to/any/dependency, relative to project root, is always the same, e.g.:

Module A
  component/my/dependency/is/here

Module B
  component/my/dependency/is/here
@itay

This comment has been minimized.

Copy link

itay commented Sep 24, 2013

@kaheglar I don't think that assumption is correct in all cases. For example, for us, we host many projects on a single instance, so you have things like:

/static/app1/components/...
/static/app2/components/...

The root is universal across all apps, and is set to /static/. It can't be changed for individual apps because there is also a global directory of pre-built things that is in there. We can't assume that the require root is relative to a particular project.

Finally, this also still ignores version independence. With a sibling structure, you cannot have multiple versions of the same library in the same app, even if they are not used on the same page. So if two components need different versions of d3, and are loaded on two separate pages, you're hosed, you can't even install it. A nested installation structure would solve this.

@danielchatfield

This comment has been minimized.

Copy link

danielchatfield commented Sep 24, 2013

Let's say you had a jquery dependency conflict then I'm sure you would agree that including two versions of jquery is not a solution to this problem, it might make development easier in the short term but your site now has to send two versions of jquery to the user and in this particular example that won't work anyway as whilst you can have 2 versions of jquery loaded you can only have one of them bound to the jQuery global.

For there to be a conflict the versions must not be semver compatible, that means that the author is indicating that the two APIs are not compatible - if you happen to know that for what you want then they are compatible then you can override it and tell bower which one to install.

There aren't very many circumstances where different versions of dependencies work, e.g.

  • Multiple javascript libraries will conflict with each other
  • CSS styles will conflict

I do understand the use-case for this (although it is one that should not be taken lightly - upgrading a component so that you can have a single dependency should be the first port of call and only when this really isn't possible should you even consider having duplicate dependencies). However I don't think nested dependencies are the way to solve this.

@itay In the example you give with d3 what happens if the two components with conflicting versions are used on the same page, or you use a build tool to concatenate the js? Surely they will conflict with each other.

@danielchatfield

This comment has been minimized.

Copy link

danielchatfield commented Sep 24, 2013

@itay You can have multiple versions of dependencies by specifying a different name:

"d3": endpoint
"d3-2": endpoint

or:

bower install <name>=<package>#<version>

@itay

This comment has been minimized.

Copy link

itay commented Sep 24, 2013

First of all, thank you to everyone for debating me. I realize that I'm in the minority and I have a specific use-case in mind, so I appreciate people taking the time to respond. I also realize Bower will most likely not change, but I can at least try :)

@danielchatfield I think about the problem a bit differently:

  1. The fact that things don't work that way today doesn't mean that they shouldn't and/or won't. I feel like we can make a real effort into making things more componentized and less dependent on global state.
  2. require.js (and other loaders) actually allow us to solve this for JS. I can require two different versions of jQuery or any library (as long as it is AMD-compliant), and have two different versions live on the page. Since I never use window.jQuery or any other global variable, I'm safe.

I completely agree on the semver part - but I don't believe that that should exclude it from two versions of the same library from working on the same page, assuming they fit (2) above.

That said, I do fully agree that there are two limitations with what I'm saying:

a. CSS is global, and there's no real way around it at the moment (modulo something like shadow DOM).
b. If a library is not AMD-compliant (e.g. Backbone), you might have some trouble with running multiple versions, even with require.js shims.

That said, I think that (a) and (b) are the minority, and something I can limit as a component author and component user. Ideally, we'd find a universal solution, but I think that something like nested dependencies would go a long way.

I think the above should address the "using two versions of the same component on a single page" discussion (at least my point of view on it). Regarding build tools: I've usually taken the approach of doing component-centric minification, and still using require to load these minified component files. And even if they were all concatenated into a single file, the require loading system should make sure that each component gets the appropriate version.

Finally, I realize that I can manually get it to install two different components, but I can't do it from a bower.json without the writer of that file being aware that he is going to have two conflicting components.

For example, if I have (in bower.json):

{
    dependencies: {
        "sankey": "1.0.0",
        "parsets": "1.0.0"
    }
}

and in the above example, let's say sankey depends on d3.v3, but parsets depends on d3.v2, then I now have to know that and actually go and change their individual component files, and their individual include paths. I don't think this is a really tenable solution.

Anyway, I hope this makes sense. Basically, to sum it up, I think that nested dependencies is a useful option - it's not right for everyone, but it is right for some use cases. By putting it there, we can allow people to more properly encapsulate their dependencies, and move the web a step forward (IMHO, of course).

  • Side note: there is one performance downside to nested dependencies, which is if two components need the same dependency (version and all), it might be included twice on the page. I think that symlinks would be a great solution to that specific problem, but it's an optimization. I want to solve the correctness issue first :)
@danielchatfield

This comment has been minimized.

Copy link

danielchatfield commented Sep 24, 2013

Finally, I realize that I can manually get it to install two different components, but I can't do it from a bower.json without the writer of that file being aware that he is going to have two conflicting components.

then I now have to know that and actually go and change their individual component files

That's not what I meant (although on re-reading it it is my fault for not explaining adequately). To clarify my previous comment. You will not need to change the individual components, you can resolve the conflict (for your particular use case) by doing the following:

  • Making one version of d3 the 'default' (it will be saved in your main bower.json file with an exclamation mark)
  • Installing the other version with a different name: bower install legacy-d3=d3#oldversion
  • Adding it to the config map (http://requirejs.org/docs/api.html#config-map)

More comments:

Components should be modular 👍
Components should not rely on global state (as far as is possible) 👍

Side note: there is one performance downside to nested dependencies, which is if two components need the same dependency (version and all), it might be included twice on the page. I think that symlinks would be a great solution to that specific problem, but it's an optimization. I want to solve the correctness issue first :)

^this is the reason why bower is like it is. Browserify uses npm and thus has nested dependencies but automatically resolves semver compatible ones when it builds it. This works for browserify because they control the build process but bower is unopinionated and can be used with any build stack (or none) so that won't work.

I think the default behaviour for bower when presented with two dependencies with a conflict should be exactly what it currently is, warn the user of the conflict and force them to manually resolve it.

Speaking hypothetically I guess it would be nice to have the ability in the resolve process to specify that you can't resolve the conflict and that you want two libraries, and then this information is exposed somehow so that build processes can (if they support it) automatically do the magic like browserify. In your case, it could automatically add the requirejs config to make it work. But this would just be automating things that you can currently do manually and wouldn't work with all build stacks which kind of breaks bowers 'unopinionated' stance.

I hope that was useful, and I think that technique (first paragraph) may solve your problem.

@itay

This comment has been minimized.

Copy link

itay commented Sep 24, 2013

@danielchatfield thank you for clarifying, it was useful.

I guess my issue with this solution is that it requires the component user to do extra things, which is either do some special installation steps (to resolve the conflicts) or add some require config, etc. The reason I like nested dependencies is that it puts the burden on the component author, because they have to specify their dependencies, and since they know their dependencies will always be installed in the same way, they can rely on it.

For example, with nested dependencies, I can have things refer to the internal component directory, rather than relying on a require.config call (which again introduces global things, since there is only a single require config). Since paths are guaranteed unique (by their nature), they allow me to specify exactly which one I want.

However, paths are only useful if they are predictable, which is why I want the option for nested dependencies.

Finally, I want to describe how we've decided to solve the problem given that bower is flat. I'll use the same example I used originally. Assume I have the following module definitions:

module-C:
    c.js:
        define(function(require, exports, module) {
            return { message: "this is C!" };
        });
    bower.json:
        {
            name: "module-C",
            dependencies: {}
        }
module-B:
    b.js:
        define(function(require, exports, module) {
            var C = require("../module-C/c");
            console.log("C's message: " + C.message)
            return { message: "this is B!" };
        });
    bower.json:
        {
            name: "module-B",
            dependencies: {
                "module-C": "1.0.0"
            }
        }
module-A:
    a.js:
        define(function(require, exports, module) {
            var B = require("../module-B/b");
            console.log("B's message: " + B.message);
            return { message: "this is A!" };
        });
    bower.json:
        {
            name: "module-A",
            dependencies: {
                "module-B": "1.0.0"
            }
        }
module-D:
    D.js:
        define(function(require, exports, module) {
            var B = require("../module-c/c");
            console.log("C's message: " + C.message);
            return { message: "this is D!" };
        });
    bower.json:
        {
            name: "module-D",
            dependencies: {
                "module-C": "1.0.0"
            }
        }

My app structure looks like this:

apps/
    myapp/
        bower.json:
            {
                dependencies: {
                    "module-A": "1.0.0",
                    "module-D": "1.0.0"
                }
            }

Now, when I run bower install for my app, I'll get this:

apps/
    myapp/
        bower.json
        components/
            module-A/
                a.js
                bower.json
            module-B/
                b.js
                bower.json
            module-C/
                c.js
                bower.json
            module-D/
                d.js
                bower.json

Note how I never have to use a require.config call - I rely on the paths to
load the right thing. I lose two things in this scheme:

  1. Version independence
  2. The ability to have the same environment when developing a component and
    deploying it in the app.

I can live with (2), but losing (1) is really unfortunate.

I know that some people think that relying on actual paths rather than require.config
calls is bad, but I respectfully disagree :) It's the only way I can have
reliable importing without requiring the end-user to do something, and it also
removes more global names from the page.

@sheerun

This comment has been minimized.

Copy link
Contributor

sheerun commented Apr 16, 2014

#1236 implements nested dependencies through symlinks.

Real nested dependencies will never be implemented. Sorry guys.

If you need to @import different version of component in each project, write bower.json for each.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment