Live data from Hacker News

Show HN: Duo – a next-generation package manager for the front end

duojs.org

91–100 of 103 posts

Re: Show HN: Duo – a next-generation package manager for the front end

#91

Earlier quoted context omitted.

Author here. @tomdale We're on the same page. I've worked with many large JS codebases as well and consistency and repeatability are a must. However, as with any tool, it's effectiveness comes from how you use it. There are so many NPM modules that use "*" in their manifests. That's not very robust. Having a manifest isn't a solution, it's how you use the manifest. In Duo you can pin down any dependency right in the…

> In Duo you can pin down any dependency right in the file via require('some/repo@tag'), which is the same as having a manifest with a pinned version. That will do nasty things for your ecosystem if people start doing that in libraries. You'll end up with version lock on shared dependencies: - My app depends on foo and bar. - foo and bar both depend on baz. - They both specify different tags. Now your solver has a sa…

This isn't a problem on server side javascript where packages can have different versions of the same dependency.

This may not be a problem on client side javascript if your dependencies don't export to the global object.

Re: Show HN: Duo – a next-generation package manager for the front end

#92
post #81

I am a little confused about how Duo actually transforms dependencies into JS values. When I write, say, var uid = require('matthewmueller/uid'); as in the home page example, what gets bound to the uid identifier? The point is that a package manager does not only need to fetch dependencies, but also to specify relations between modules. This is why, for instance, Bower only does half the story (fetching) and it has t…

The flow is...

1) Download https://github.com/matthewmueller/uid from github

2) Find it's entry using https://github.com/duojs/main

3) Parse it's entry, and extract the dependencies

4) Recurse

Re: Show HN: Duo – a next-generation package manager for the front end

#93

Earlier quoted context omitted.

Sorry, I think I'm missing something because I don't see the problem -- why can't foo and bar just use different versions of baz? Is it not possible to do that?

Let's say your app my_app uses foo and bar. They both use baz. You give them each their own version of baz: foo gets baz 1.0 and bar gets baz 2.0. Now, at runtime: my_app calls foo.giveMeAWidget() foo calls new baz.Widget() on its 1.0.0 version of baz my_app gets that back then it calls bar.doSomethingWithAWidget(widget) and passes it in bar calls baz.flummoxAWidget(widget) and passes the widget in baz.flummoxAWidget…

"Doctor, it hurts when I do this."

What you're describing is an inappropriate intimacy antipattern.

Re: Show HN: Duo – a next-generation package manager for the front end

#94

Earlier quoted context omitted.

Let's say your app my_app uses foo and bar. They both use baz. You give them each their own version of baz: foo gets baz 1.0 and bar gets baz 2.0. Now, at runtime: my_app calls foo.giveMeAWidget() foo calls new baz.Widget() on its 1.0.0 version of baz my_app gets that back then it calls bar.doSomethingWithAWidget(widget) and passes it in bar calls baz.flummoxAWidget(widget) and passes the widget in baz.flummoxAWidget…

"Doctor, it hurts when I do this." What you're describing is an inappropriate intimacy antipattern.

Because people keep pinging me on twitter about this, I feel like I ought to explain a bit more.

What's going on in the pattern described is that you're getting a `BazWidget1` object from the `foo` module. Then, you're passing that object you got from `foo` into `bar`, which passes it to `Baz2Flummox(BazWidget2 widget)`.

Why are you flummoxing widgets that you don't know the origin of?

What kind of program needs to mutate state on a object, by passing it from one object-mutator to another? Why do you not have clearly defined ownership of objects, and clearly defined functions that take arguments and return values?

"Inappropriate Intimacy" is a code smell where one class is delving into the inner workings of another, depending on things that ought to be private. The `BazWidget` should be a private implementation detail of `foo` and `bar`, but instead, we are passing this implementation detail from one function call to another.

More specifically, we have `BazWidget1` and `BazWidget2` objects being used interchangeably. It's tempting to blame this on the package manager or module system, but it is just a badly designed program.

This is one class of errors that a "strongly" typed language can often detect and prevent at design time. However, I've seen C++ and Java programs with the same problematic antipattern, just with more layers of Interface wrapping. And, whatever, JavaScript is what it is, and is not "strongly" typed. It lets you pass any value as any argument to any function, and leaves it up to the callee to decide what to do with it.

Personally, I have no strongly held opinion about who's best to handle this responsibility: the compiler, the caller, or the callee. There are benefits to "loosely" typed languages as well, and I'd rather not make this about that.

I've referred to this sort of thing as a "gumby baton". You're passing an object from one worker to another, and each one mutates it a little bit, like runners in a relay race where the baton is made of clay, so it gets the finger print of each worker in turn.

This is a terrible antipattern! This is how we end up with middleware depending on other middleware having been executed in exactly the right order. It is terrible for reasoning about program behavior, and results in unexpected behavior when workers are combined in novel ways. Making programs harder to reason about makes security virtually impossible, and increases the cost of maintenance and re-use. Even up front, it is a challenging pattern to use in building an application, though it sounds appealing in principle if you've never been handed a warped and mangled baton.

So, when I say "Doc, it hurts when I do this", I'm implying that the proper response is "Don't do that".

Ultimately, it's not the compiler's fault. Gumby batons exist in C++ and Java and C and are even possible in pure functional languages. Be on the lookout for it. The compiler won't protect you. The module system won't protect you. You have to use your human brain.

Another caveat just to avoid any "tu quoque" responses: I've made this mistake (and sworn to never do it again!) many times. The most egregious offender in Node is mutating the req and res objects. But, it can be very subtle and hard to spot in the initial design. We just fixed a bunch of really subtle bugs in the lockfile module by changing how it was handling the options object, because it had taken on a gumby-baton behavior internally.

Re: Show HN: Duo – a next-generation package manager for the front end

#95

Earlier quoted context omitted.

"Doctor, it hurts when I do this." What you're describing is an inappropriate intimacy antipattern.

Because people keep pinging me on twitter about this, I feel like I ought to explain a bit more. What's going on in the pattern described is that you're getting a `BazWidget1` object from the `foo` module. Then, you're passing that object you got from `foo` into `bar`, which passes it to `Baz2Flummox(BazWidget2 widget)`. Why are you flummoxing widgets that you don't know the origin of? What kind of program needs to m…

Mutation isn't necessary to demonstrate the problem. Consider three libraries: first, there's a basic, widely-used datetime library. There's also a timezone library depending on datetime 1.0 and a dateformat library depending on datetime 2.0.

  # Get the current time in the PST time zone (returns a 1.0 object)
  now = timezone.now_in_zone("PST")
  # Format the date for display (accepts a 1.0 object but depends on 2.0)
  formatted = dateformat.format(now)
Now the problem: imagine that datetime 2.0 switched from one-indexed months (1 is January) to zero-indexed months (1 is February). The timezone library depends on datetime 1.0, so it used "1" to indicate January, giving me a datetime value with month=1. The dateformat library depends on datetime 2.0, so it incorrectly interprets that month=1 as February. All of my January dates will now be incorrectly formatted as February.

(Switching month representation is a rather drastic example, but it's also clear. Substitute a more subtle data format change if you'd like.)

There's no mutation here, and the dependency graph is trivial. I just received a datetime from one library and passed it to another library.

It's possible that I'm missing something, but I've been asking Node users about this for a couple years and I usually get blank stares. I also have no horse in this particular race. Vendoring seems like a great idea to me, but I fear the uncertainty of this version mismatch situation.

Re: Show HN: Duo – a next-generation package manager for the front end

#96

Earlier quoted context omitted.

Because people keep pinging me on twitter about this, I feel like I ought to explain a bit more. What's going on in the pattern described is that you're getting a `BazWidget1` object from the `foo` module. Then, you're passing that object you got from `foo` into `bar`, which passes it to `Baz2Flummox(BazWidget2 widget)`. Why are you flummoxing widgets that you don't know the origin of? What kind of program needs to m…

Mutation isn't necessary to demonstrate the problem. Consider three libraries: first, there's a basic, widely-used datetime library. There's also a timezone library depending on datetime 1.0 and a dateformat library depending on datetime 2.0. # Get the current time in the PST time zone (returns a 1.0 object) now = timezone.now_in_zone("PST") # Format the date for display (accepts a 1.0 object but depends on 2.0) form…

The problem is here: "(accepts a 1.0 object but depends on 2.0)"

This case actually has nothing to do with nested dependencies. Your time zone lib is returning a datum with one type, which you're passing to a formatting lib that expects a datum with another type. This can happen if your libs have dependencies that are totally different libraries instead of the same library with different versions. It can also happen if your libs have no dependencies at all. This is not an issue of dependencies but of you not understanding your libs' APIs.

Re: Show HN: Duo – a next-generation package manager for the front end

#97

Earlier quoted context omitted.

Mutation isn't necessary to demonstrate the problem. Consider three libraries: first, there's a basic, widely-used datetime library. There's also a timezone library depending on datetime 1.0 and a dateformat library depending on datetime 2.0. # Get the current time in the PST time zone (returns a 1.0 object) now = timezone.now_in_zone("PST") # Format the date for display (accepts a 1.0 object but depends on 2.0) form…

The problem is here: "(accepts a 1.0 object but depends on 2.0)" This case actually has nothing to do with nested dependencies. Your time zone lib is returning a datum with one type, which you're passing to a formatting lib that expects a datum with another type. This can happen if your libs have dependencies that are totally different libraries instead of the same library with different versions. It can also happen…

In most languages, a type mismatch would always correspond directly to a type name mismatch. In e.g. Python (since it has a clear module system), if I know that f() returns a datetime.datetime, and I know that g(t) takes a datetime.datetime, then I know that they will compose. (Modulo bugs of other types, of course.)

When using NPM, I don't have that guarantee. The docs for these libraries could clearly state that they'll integrate around the datetime type, but that may be false in practice. And I'll only know that ahead of time if (1) I know that they both use datetime internally, (2) I know exactly the versions of datetime that they use, and (3) I know exactly how datetime changed in between those versions. With non-vendoring package management, I don't have to know any of these things.

Re: Show HN: Duo – a next-generation package manager for the front end

#98
I have a hard time seeing what exactly the point of having another package manager is. We already have a very established way of managing front-end packages: NPM and Browserify. I don't see why you would want any of the features that are advertised in this package manager, namely using Github repos vs. npm packages, and manifestless packages. All it gives you is a more fractured ecosystem and code that won't run on the server.

Re: Show HN: Duo – a next-generation package manager for the front end

#99

Earlier quoted context omitted.

The problem is here: "(accepts a 1.0 object but depends on 2.0)" This case actually has nothing to do with nested dependencies. Your time zone lib is returning a datum with one type, which you're passing to a formatting lib that expects a datum with another type. This can happen if your libs have dependencies that are totally different libraries instead of the same library with different versions. It can also happen…

In most languages, a type mismatch would always correspond directly to a type name mismatch. In e.g. Python (since it has a clear module system), if I know that f() returns a datetime.datetime, and I know that g(t) takes a datetime.datetime, then I know that they will compose. (Modulo bugs of other types, of course.) When using NPM, I don't have that guarantee. The docs for these libraries could clearly state that th…

You've correctly identified a guarantee that you don't have with npm. In practice, anecdotally, I've never run into this issue, while I have many times and with much pain dealt with conflicting deep dependencies using bower and bundler. Which may explain the blank stares. It's a tradeoff I am personally happy to make.

Re: Show HN: Duo – a next-generation package manager for the front end

#100

Earlier quoted context omitted.

Sorry, I think I'm missing something because I don't see the problem -- why can't foo and bar just use different versions of baz? Is it not possible to do that?

Let's say your app my_app uses foo and bar. They both use baz. You give them each their own version of baz: foo gets baz 1.0 and bar gets baz 2.0. Now, at runtime: my_app calls foo.giveMeAWidget() foo calls new baz.Widget() on its 1.0.0 version of baz my_app gets that back then it calls bar.doSomethingWithAWidget(widget) and passes it in bar calls baz.flummoxAWidget(widget) and passes the widget in baz.flummoxAWidget…

In this situation, "foo" or "bar" or both should not depend on baz, but have it as a peer dependency. There is nothing broken, just a very specific case not so easily identifiable, but very easily fixable.
Post reply on HN