aspnet / LibraryManager

MIT License
458 stars 83 forks source link

Cannot restore. Multiple definitions for libraries - Workaround? #330

Closed nickalbrecht closed 4 years ago

nickalbrecht commented 6 years ago

Just curious why the change was made a while back to prevent multiple definitions for libraries? And if there's a work around, regression possible, etc.

For example. I have an application that is meant for internal users only but does have one page meant for anonymous submissions. In order to have it appear to have the same visual style as a pre-existing public facing website (and not the internal application) I'm loading the different assets (layout-public.cshtml) for example since I'm using MVC. Here's a portion of my libman.json file for my project. Problem is once you've got any library defined more than once, the entire process refuses to run for any other libraries as well.

{
  "defaultProvider": "unpkg",
  "libraries": [
    {
      "library": "jquery@3.3.1",
      "files": [
        "jquery.min.js",
        "jquery.js"
      ],
      "destination": "wwwroot/vendor/jquery"
    },
    {
      "library": "twitter-bootstrap@4.1.3",
      "files": [
        "css/bootstrap.min.css",
        "css/bootstrap.css",
        "js/bootstrap.bundle.js"
      ],
      "destination": "wwwroot/vendor/bootstrap"
    },
    {
      "library": "jquery@3.3.1",
      "files": [ "jquery.min.js" ],
      "destination": "wwwroot/public/jquery"
    },
    {
      "library": "twitter-bootstrap@3.3.7",
      "files": [
        "js/bootstrap.min.js"
      ],
      "destination": "wwwroot/public/bootstrap"
    }
  ],
  "version": "1.0"
}

jQuery I can consolidate, they just have different destinations currently, just means I have to modify the layout/style links pulled in my layout-public.cshtml. Bootstrap on the other hand is a completely different library version, with a different destination. Is it possible to at least loosen up the check for multiple definitions of a library to consider the version. Be nice if it was a warning instead of an error, but that still wouldn't complete solve my issue.

My last option is that I commit the assets for the public facing page into source control, and leave libman for the main application, but I'd like to avoid that if possible and wanted to provide feedback here first.

jimmylewis commented 6 years ago

@justcla can you chime in here?

justcla commented 6 years ago

Hi Nick

Thanks for your feedback.

Ultimately, the biggest issue with supporting multiple versions of a library was finding a good syntax for updating the library from the CLI. At the time, we decided that it would be simpler to not support multiple versions of a library. Much of that decision was based on an assumption that it would be rare to use multiple versions of a library in a single web project. Your scenario highlights a very real case where you need multiple versions, so we'll need to seriously reconsider our position on supporting multiple versions. Thanks for bringing it to our attention.

The feature is still in preview. Once it's released into the wild, we'll be watching closely for more feedback from users wanting multiple library versions in their web project. It's likely we'll revert to allowing this behavior if we see more requests for it.

For now, there are two known work-arounds.

  1. As you mentioned, keep one version of files checked into source control and use LibMan for the other.
  2. You can create a new libman.json file for the vendor files, place it in the wwwroot/vendor directory (change the destination paths to be relative to the folder libman.json is in), then restore the vendor libman.json file from the command line.

As a side note, can you tell me what you want most from LibMan?

And how often do you anticipate needing each or any of these benefits?

Thanks for you continued interest and feedback with LibMan. :-D

nickalbrecht commented 6 years ago

Thanks for the response.

I might look into your second suggestion for a workaround, I wasn't sure if LibMan supported multiple json files or not. So far I've managed to avoid needing to create a more involved build script than simply doing dotnet build, but if this is what pushes a more involved build process, than so be it :-P I suspect in the short term I may go with the first one though, and simply put just the public facing layout's assets into source control.

I would say the main benefit I use libman for is to avoid packaging third party resources into our source control, and to cherry pick only the assets needed from the libraries. We're never going to modify the files ourselves, so they only change when there's a new version. So 'change-tracking` those files in our source control just seems silly. Prior to moving my project to use LibMan I was using Bower, and the amount of extra 'stuff' it pulled down/included for deployment was unnecessary to say the least. I only needed the distribution files, not the full source Bower typically provided. Also, most libraries are starting to come with multiple distributions depending on your environment and/or level of JS version supported. So while there might be a dozen files in a distribution folder, I may only need 3 or 4. I'm aware there are other tools to do similar tasks (webpack, yarn) but the investment required to learn/get that setup 'right' is non trivial, and definitely not as trivial as LibMan makes a similar result. So that's why I use LibMan and where I typically get the most use out of it.

jimmylewis commented 6 years ago

I wasn't sure if LibMan supported multiple json files or not.

In most cases it does not, but the CLI tool works on a libman.json file in any folder. You'll have to invoke it for each of those, and you won't see it work well in VS, but it would work for your situation if you're just editing the files manually anyway.

craiggeil commented 6 years ago

Same problem here. I need the old bootstrap-theme.css files from 3.3.7 but am using the current bootstrap. I have the 3.3.7 css files going to a different folder too. It was a poor decision to not allow this. You're competing against npm which has no restrictions like this. Although npm forces you to download thousands of unnecessary dependencies to an unmodifiable folder.

jimmylewis commented 6 years ago

@craiggeil I'm trying to better understand your case. You're using bootstrap 4.x, and only the boostrap-theme.css file from 3.3.7? How would you accomplish this with npm? I thought you could only have a single bootstrap dependency in your package.json; when I try to have two, it NPM removes all but the last one.

The workaround we're currently proposing is to have multiple libman.config files within your project, and restore them using the libman dotnet tool. Is this not viable in your scenario?

craiggeil commented 6 years ago

Thanks for the response. See the old bootstrap theme works just fine with the new version 4.x. Yes you can use npm to pull down different versions with it's json file. I think I'll pass on this and stick with npm. This feels too much like beta software right now. For instance the unpkg selection does not work at all. Thanks and good luck!

KINGH242 commented 6 years ago

Same issue here. My use case is in an old MVC 5 project and I am wanting to update bootstrap, but place the css files in the Content/lib/bootstrap and the js files in Scripts/lib/bootstrap.

Is there any current workaround for this scenario?

flemingtech commented 6 years ago

I've got the same issue even with the same version of the library. I need to move css files into /Content and js files into /Scripts. Yeah I can have two library configuration files, one in in /Scripts and one in /Content, but that seems to have it's own set of problems in Visual Studio.

Yet the following fails.

{ "version": "1.0", "defaultProvider": "cdnjs", "libraries": [ { "library": "foundation@6.4.3", "destination": "Scripts/Foundation", "files": [ "js/foundation.js" ] }, { "library": "foundation@6.4.3", "destination": "Content/Foundation", "files": [ "css/foundation.css" ] } ] }

jeremycook commented 6 years ago

@justcla my use case matches @flemingtech's https://github.com/aspnet/LibraryManager/issues/330#issuecomment-431955450. I want to place files from one library and version in two different folders within one VS Project. Even though there is a workaround (thank you) for me this is a bug that I would greatly appreciate being addressed.

To answer one of your questions to nickalbrecht, here is how I benefit from libman:

jimmylewis commented 6 years ago

The main reason we removed the option of having multiple definitions for the same library was that it started making our code much more complicated around handling updates, especially if the separate entries had different versions. (And believe me, it was a hotly contested topic.) We could revisit that, but there were a number of bugs as we were using it.

Just as a thought exercise, would it work if we could change the files part to include a destination as well?

"files": [
    { "css/foundation.css": { "destination": "Content/Foundation" } },
    { "js/foundation.js": { "destination": "Scripts/Foundation" } }
]

This would also work really nicely with pattern matching (once implemented).

However, it doesn't solve the desire to have separate versions of the same library, like the original bootstrap 4.1.3/3.3.7 in the OP, but it does seem a lot easier to address.

jeremycook commented 6 years ago

@jimmylewis what you propose would cover my specific case, but yes not the OP's.

jeremycook commented 6 years ago

I'd add my case would look more like this. I need a copy of a library in two different folders.

"files": [
    { "**": { "destination": "SomeFolder/Foundation" } },
    { "**": { "destination": "Some/Other/Folder" } }
]
flemingtech commented 6 years ago

@jimmylewis, I like the principal idea of having a source+target map for the files. It does complicate the overall syntax for just this use case however.

Since it's just a thought exercise... What if the initial "destination" tag was, for lack of a better word, the root destination. Then the individual destinations, when provided, were relative to the root destination? It might make the syntax simpler for both use cases.

OrbintSoft commented 5 years ago

Same issue, I need to use old bootstrap in a area of my website:

    {
      "provider": "cdnjs",
      "library": "twitter-bootstrap@4.1.3",
      "destination": "wwwroot/lib/twitter-bootstrap/",
      "files": [
        "js/bootstrap.js",
        "css/bootstrap-grid.css",
        "css/bootstrap-reboot.css",
        "css/bootstrap.css"
      ]
    },
    {
      "provider": "cdnjs",
      "library": "twitter-bootstrap@3.3.7",
      "destination": "wwwroot/lib/twitter-bootstrap-old/",
      "files": [
        "css/bootstrap.css"
      ]
    }

WORKAROUND:

I load latest boostrap from cdnjs and old boostrap from unpkg:

    {
      "provider": "cdnjs",
      "library": "twitter-bootstrap@4.1.3",
      "destination": "wwwroot/lib/twitter-bootstrap/",
      "files": [
        "js/bootstrap.js",
        "css/bootstrap-grid.css",
        "css/bootstrap-reboot.css",
        "css/bootstrap.css"
      ]
    },
    {
      "provider": "unpkg",
      "library": "bootstrap@3.3.7",
      "destination": "wwwroot/lib/twitter-bootstrap-old/",
      "files": [
        "dist/css/bootstrap.css"
      ]
    }
andresteyn commented 5 years ago

@OrbintSoft :: Thanks for the workaround, it works perfectly.

@Devs :: Great library, so much easier to use than Bower and NPM. With regard to multiple versions, we are currently transitioning from Bootstap 3 to 4 on a fairly complex site and have some pages referencing 3 and others 4. I would not think that this is an uncommon scenario, so I would vote for multiple version support.

tb-mtg commented 5 years ago

Any idea on if this is going to be implemented?

I tool would require multiple version support for certain libraries.

mqudsi commented 5 years ago

I wanted to use libman to install and update both a modern version of JQuery and a legacy version of JQuery used for older IE versions.

A suggested approach to the problem described as the blocker for this issue by @justcla is to add a an optional "id" field that supplants the package name when provided, and lookup/deduplicate entries by that value.

tb-mtg commented 5 years ago

@mqudsi @justcla why not just use by both package name & version to lookup/de-duplicate entries.

mqudsi commented 5 years ago

Because that means when using the command line to update a dependency

a) you would need to know what version it is currently at in order to update it to the latest or a new particular version b) you can't use muscle memory: the first time you update it you use foo@bar but the second time you update it you need to use foo@baz

jimmylewis commented 5 years ago

Exactly as @mqudsi says, having to always use the full library id foo@1.2.3 on the CLI made it nearly unusable without having the libman.json file already open in an editor.

Also if you happened to update the older version to coincidentally now match another one, you end up with two libraries with the same ID (e.g. libman update jquery@2.2 => jquery@3.3 which could already exist; do we merge them? What if they had different files specified, destinations, etc...)

Again, I'll refer to NPM's behavior where if you specify the same package twice, it will just remove the duplicates (last one wins). I don't think that's necessarily desirable (running the tool edits your file). I'd love an example of a tool that does support multiple versions of the same library and we can consider a similar behavior on our side.

tb-mtg commented 5 years ago

It knows to remove old folders if i remove a library or change a destination in libman.json,

What can't it know what to do if i change the version?

nickalbrecht commented 5 years ago

Just ran into this again when working on CI/CD.

Just an idea, what if there was a way to alias the dependencies in the libman.json file, so that when communicating with the provider they would use the real name, but internally for handling updates/removing/changes it used the alias you define (when present). Something akin to...

{
  "defaultProvider": "unpkg",
  "libraries": [
    {
      "library": "jquery@3.3.1",
      "alias":"jquery-internal",
      "files": [
        "jquery.min.js",
        "jquery.js"
      ],
      "destination": "wwwroot/vendor/jquery"
    },
    {
      "library": "jquery@3.3.1",
      "alias":"jquery-public",
      "files": [ "jquery.min.js" ],
      "destination": "wwwroot/public/jquery"
    },
    {
      "library": "twitter-bootstrap@4.1.3",
      "alias":"bootstrap-internal",
      "files": [
        "css/bootstrap.min.css",
        "css/bootstrap.css",
        "js/bootstrap.bundle.js"
      ],
      "destination": "wwwroot/vendor/bootstrap"
    },
    {
      "library": "twitter-bootstrap@3.3.7",
      "alias":"bootstrap-public",
      "files": [
        "js/bootstrap.min.js"
      ],
      "destination": "wwwroot/public/bootstrap"
    }
  ],
  "version": "1.0"
}
mqudsi commented 5 years ago

@nickalbrecht https://github.com/aspnet/LibraryManager/issues/330#issuecomment-465769429 :)

drauch commented 5 years ago

👍 we would need this feature as well. Please consider implementing it!

nickalbrecht commented 4 years ago

Just checking in on past issues. For my specific scenario, the situation is no longer relevant as the dependency that needed the old version Bootstrap was updated/removed. But I wasn't sure if I should close my issue since multiple other people were encountering similar situation, but it doesn't seem like there's going to be any traction on this one without some specific stories to demonstrate how it would work, and not break other behaviours & patterns. For example, CLI commands.

@jimmylewis Would you prefer I left this open?

OrbintSoft commented 4 years ago

Please keep it open because still an issue

Ottieni Outlook per Androidhttps://aka.ms/ghei36


From: Nick Albrecht notifications@github.com Sent: Thursday, January 9, 2020 2:32:44 AM To: aspnet/LibraryManager LibraryManager@noreply.github.com Cc: Stefano Balzarotti stefano.balzarotti@orbintsoft.net; Mention mention@noreply.github.com Subject: Re: [aspnet/LibraryManager] Cannot restore. Multiple definitions for libraries - Workaround? (#330)

Just checking in on past issues. For my specific scenario, the situation is no longer relevant as the dependency that needed the old version Bootstrap was updated/removed. But I wasn't sure if I should close my issue since multiple other people were encountering similar situation, but it doesn't seem like there's going to be any traction on this one without some specific stories to demonstrate how it would work, and not break other behaviours & patterns. For example, CLI commands.

@jimmylewishttps://github.com/jimmylewis Would you prefer I left this open?

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHubhttps://github.com/aspnet/LibraryManager/issues/330?email_source=notifications&email_token=ABQVJ7DYA2Q5QRRS2M7OTNLQ4Z5DZA5CNFSM4FMCK6H2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEIOTI7A#issuecomment-572339324, or unsubscribehttps://github.com/notifications/unsubscribe-auth/ABQVJ7F4XP7IXQUL6CEIILLQ4Z5DZANCNFSM4FMCK6HQ.

jimmylewis commented 4 years ago

@nickalbrecht I'm fine with leaving this open for now, mostly to accumulate feedback and ideas. As commented above, I think this is still boiling down to a few main points (until we have a specific scenario identified that isn't already served in some capacity):

ole1986 commented 4 years ago

No proper solution for almost 2 years not allowing us to use same packages with different versions?! Sorry, but this is just WoW!

Even no workaround because choosing different providers does not resolve it

jimmylewis commented 4 years ago

@ole1986 the current workaround is to use multiple libman.json files and use the libman CLI tool to restore each of them. I'd like to have a better way of handling this, but with our current design, it creates a bunch of issues with how the tooling currently works. I don't want to reopen that can of worms without a firm plan for how to prevent it from creating confusing failure behaviors.

I'm not aware of any other package managers that support multiple entries for a single library. If you have a working example of another one that we could consider modeling after, I'd be willing to take a look.

ole1986 commented 4 years ago

Since libman is using different providers. why not allowing it at least when the provider is different?!

Avaruz commented 4 years ago

@nickalbrecht I do this in my project and works, I put the entire boostrap in a bootstrap/dist folder and copy to the other folder with a filesystem provider, now I can have the bootstrap css and js separatly:

{
  "version": "1.0",
  "defaultProvider": "cdnjs",
  "libraries": [
    {
      "library": "jquery@3.5.1",
      "files": [ "jquery.min.js", "jquery.min.map" ],
      "destination": "wwwroot/lib/jquery/"
    },
    {
      "library": "twitter-bootstrap@4.5.0",
      "alias": "bootstrapjs",
      "destination": "wwwroot/lib/bootstrap/dist/",
      "files": [
        "css/bootstrap.min.css",
        "css/bootstrap.min.css.map",
        "js/bootstrap.bundle.min.js",
        "js/bootstrap.bundle.min.js.map"

      ]
    },
    {
      "library": "jquery-validation-unobtrusive@3.2.11",
      "destination": "wwwroot/lib/jquery-validation-unobtrusive/",
      "files": [ "jquery.validate.unobtrusive.min.js" ]
    },
    {
      "library": "jquery-validate@1.19.1",
      "destination": "wwwroot/lib/jquery-validation/",
      "files": [ "additional-methods.min.js", "jquery.validate.min.js" ]
    },
    {
      "provider": "filesystem",
      "library": "wwwroot/lib/bootstrap/dist/css/",
      "destination": "wwwroot/css/"
    },

    {
      "provider": "filesystem",
      "library": "wwwroot/lib/bootstrap/dist/js/",
      "destination": "wwwroot/lib/bootstrap/"
    }
  ]
}
nickalbrecht commented 4 years ago

You're copying assets from the same version (4.5) to two different locations. The original issue was for trying to use two different versions (3.x and 4.x) of one library in the same project. My personal issue was eventually resolved by my project being upgraded to entirely use 4.x, but the original caveat mentioned in the original issue is still relevant.

As was mentioned by some of the authors, the change was done to simplify the upgrading process of libraries and not having to specify the which version of a library to upgrade. There are work arounds mentioned in this issue to try and mitigate the problem, but I personally don't expect the change that caused the problem with having two different versions of the same library in a single file, to be reverted. It's likely here to stay at this point.

Seabizkit commented 2 years ago

can this be reopened its not resolved at all

jimmylewis commented 2 years ago

@Seabizkit this is highly unlikely to warrant a re-visit. We had a lot of bug fallout when we tried this and it worsened multiple experiences, which is why it ended with reverting the changes. The workaround (multiple libman.json files used with the CLI) seems to solve the problem, even if not elegantly, but without making as many sacrifices for a niche case.

alex-jitbit commented 1 year ago

We're migrating a big project from Vue2 to Vue3 and this would be a great feature to have :( Too bad you decided against it.

The multiple sources workaround does not work if the library is named the same at both cdnjs and unpkg

carsten-riedel commented 5 months ago

you can achieve the desired behaviour with a custom initial target, this will work on build. Add nuget. Microsoft.Web.LibraryManager.Build

Same trick as above, multiple libman.json files.

<Project InitialTargets="LibraryManagerRestoreAdditional" Sdk="Microsoft.NET.Sdk.Web">
    <Target Name="LibraryManagerRestoreAdditional" >

        <Microsoft.Web.LibraryManager.Build.RestoreTask
            FileName="libman1.json"
            ProjectDirectory="$(MSBuildProjectDirectory)"
            ProviderAssemblies="$(LibraryProviderAssemblies)">

            <Output TaskParameter="FilesWritten" ItemName="_FilesWritten"/>
        </Microsoft.Web.LibraryManager.Build.RestoreTask>

        <ItemGroup>
            <FilesForPackagingFromProject  Include="%(_FilesWritten.Identity)">
                <DestinationRelativePath>%(_FilesWritten.Identity)</DestinationRelativePath>
            </FilesForPackagingFromProject>
        </ItemGroup>

        <Microsoft.Web.LibraryManager.Build.RestoreTask
            FileName="libman2.json"
            ProjectDirectory="$(MSBuildProjectDirectory)"
            ProviderAssemblies="$(LibraryProviderAssemblies)">

            <Output TaskParameter="FilesWritten" ItemName="_FilesWritten"/>
        </Microsoft.Web.LibraryManager.Build.RestoreTask>

        <ItemGroup>
            <FilesForPackagingFromProject  Include="%(_FilesWritten.Identity)">
                <DestinationRelativePath>%(_FilesWritten.Identity)</DestinationRelativePath>
            </FilesForPackagingFromProject>
        </ItemGroup>

    </Target>