Part 1: Haxelib gets painful
I’ve been using Haxe for a long while, and for about 2-3 years I was using Haxe full time, building web applications in Haxe, so I know how important managing your dependencies is, and I know how painful it was with Haxelib, especially if you had a lot of dependencies, a lot of projects, or needed to collaborate with people on different computers.
Haxelib is okay when you’re just installing one or two libraries, and they’re libraries with stable releases where you don’t change versions often, and if you don’t need to come back to your code after long gaps in time. Basically, haxelib is fine if you’re doing weekend hackathons or contests like Ludum Dare where your projects probably aren’t too complex, you’re not collaborating with too many other people, you’re using existing frameworks, and you don’t have to worry about if it will still work fine in 4 months time. Otherwise, it can be quite painful.
I tried to help with Haxelib at one point in time (I still am in the top 4 contributors on Github, most of that was back in 2013 though), but it proved pretty unruley – even skilled developers were afraid of changing too much or refactoring in a way that might break things for thousands of developers. And some changes were impossible to make without first changing the Haxe compiler. So it’s largely sat in the “too hard” basket and has not had many meaningful improvement since it first became it’s own project in 2013.
(No offense to anyone who has been working on it – you are a braver soul than I! But I think we all agree it’s not as good as it needs to be.)
Since mid 2016, I have been working in other jobs where I don’t use Haxe full time, instead spending more time with JS: using tools like NPM, Yarn, Webpack. And they’re certainly not perfect when it comes to dependency management, but there are a few things that they do right (Yarn especially).
Part 2: What the JS ecosystem gets right.
In Node JS land (and eventually normal JS land), there was a package manager called NPM – Node Package Manager. It had a registry of packages you could install. It would also let you install a package from Github or somewhere else. The basic things.
Here’s what I think it did right:
- Used a standard format (
package.json
) to describe which packages a project uses. - Put all of the libraries in a standard location (
node_modules/${my_cool_lib}/
) - NodeJS didn’t care if you used NPM or not. As long as your stuff was in
node_modules
, it would be happy.
Why was this a good move? Because it allowed some talented people to build a competitor to NPM, called Yarn. By having simple expectations, you can have two competing package managers, and innovation can happen. Woo!
Yarn is what I use at work on a big project with 119 dependencies (and about 1000 sub-depedencies). Here’s what yarn did right:
- Reproducible builds. While package.json has information about which version I want (say, React 16.* or above), Yarn would keep information in a file called
yarn.lock
which says exactly which version I ended up using (say, React 16.0.1). This was when my friend joins the project and tries to install things she won’t accidentally end up on a newer or older version than me – Yarn makes sure we’re all using exactly the same version, and all of our dependencies and sub-dependencies are also exactly the same. - A global cache. When Yarn came out, it was several times faster than NPM on our project because it kept a cache of dependencies and was able to resolve them quickly when switching between projects and branches. NPM has caught up now – but that’s the benefit of competition!
Part 3: Introducing lix (and its friends: switchx and haxeshim)
In 2015 I remember chatting to my friend Juraj Kirchheim (also one of the key contributors who just kind of gave up) about what an alternative might be, and he described something that sounded great, a futuristic utopian alternative to haxelib.
2 years later, and it turns out, it’s been built! And it’s called “lix”.
(What’s with the name? I’m guessing it is short for “LIbraries in haXe”, a leftover from when every Haxe project needed an X in it for cool-ness, and Haxe was spelt as haXe. That, and the lix.pm
domain name must have been available).
Lix also depends on two other projects: haxeshim
and switchx
. The names aren’t super obvious, so here is my understanding of how it all works:
-
Haxe Shim intercepts calls to Haxe and does some magic. The Haxe compiler on it’s own explicitly calls
haxelib
, so you literally can’t replace haxelib without intercepting all calls to the compiler and getting rid of-lib
arguments. So haxeshim is a shim that intercepts Haxe calls and sorts out-lib
arguments so that haxelib is never needed.As a bonus, it also supports switching to the right version of Haxe for the current project. But for that, we also need “switchx”.
-
SwitchX lets you pick the Haxe version you need for your project, and automatically switches Haxe versions for whatever project you’re in. If you change between project A, on Haxe 3.4.3, and project B, in a different folder and running Haxe 4, it will always use the correct one.
How?
When you start a project you run
switchx scope create
. This makes a.haxerc
file which says that this folder is a specific project, or “scope”, and should use the Haxe version defined in the.haxerc
file.How do you change the version?
You run
switchx use latest
orswitchx use stable
orswitchx use nightly
orswitchx use 3.4.3
etc. It lets you instantly switch between different versions, and for the correct version to always be used while you’re in your project folder.Nice!
-
Lix is a package manager that you use to install packages. It is made to work with Haxe Shim, and creates a “haxe_libraries” folder, with a new hxml file for each dependency you install. It’s super fast because it uses a global cache (like yarn) and it makes sure you always have the correct version installed (like yarn). It supports installing dependencies from Haxelib, Github, Gitlab or HTTP (zip file). Anytime you update or change a dependency, one of the
haxe_libraries/*.hxml
files will be updated, you commit this change to Git, and it will update for all of your coworkers as well. Magic.
These tools are (for now) built on top of NodeJS, so you can install them with NPM or Yarn.
If you want to install each of these, you basically run these commands (warning: these will replace your current Haxe installation):
# Install all 3 tools and make their commands available.
yarn global add haxeshim switchx lix.pm
# Create a ".haxerc" in the current directory, informing haxeshim that
# this project should use a specific version of Haxe and specific
# `haxe_libraries` dependencies
switchx scope create
# Use the latest stable version of Haxe in this project.
switchx install stable
Part 4: What lix can do that haxelib cannot do (well).
With this setup, here’s what I can do that I couldn’t do before:
Be certain that I always have the exact right version installed, even if the project is being set up on someone else’s machine. Even if I pulled from a custom branch, using something like lix install github:haxetink/tink_web#pure
(install the latest version of tink_web from the “pure” branch), when I run this on a different machine, it will use not only the same branch, but the exact same commit that it used on my machine, so we will be compiling the exact same code.
Easily get up and running on a machine where they don’t even have haxe installed. I tried this today – took a project on Linux, and set up its dependencies in Lix. It used a combination of Haxelib, Github, Gitlab, and custom branches. It was a nightmare with Haxelib. I also added haxeshim, switchx and lix.pm as “devDependencies” so they would be installed locally when I ran yarn intall
. I opened a Windows machine that had Git installed, but not haxe, cloned the repo, and yarn install
. It installed all of the yarn dependencies, including haxeshim, switchx, and lix, and then running lix download
installed all of the correct “haxe_libraries”, and then everything compiled. Amazing!
Know if I’ve changed a dependency Today I was working on a change for haxe-react. In the past I would have used haxelib dev react /my/path/to/react-fork/
. Now I edit haxe_libraries/react.hxml
and change the class path to point to the folder my fork lives in. The great thing about doing this is, Git notices that I’ve changed it. And so when I go to commit the work on my project, git lets me know I’ve got a change to “react.hxml”, I’ve changed that dependency. In this case, I knew what to do: push my fork to Github, and then run lix install gh:jasononeil/haxe-react#react16
to get Lix to properly register my fork in a way that will work with my project going forward. I then commit the change, and people who use my project will get the up-to-date fork.
Start a competing package manager The great thing about all of this, is that “lix” has some great features, but if I want to write better ones, I can. Because of the way “haxeshim” just expects dependencies to haxe a “haxe_libraries/*.hxml” file, I could write my own package manager, that does things in my own way, and just places the right hxml file in the right place, and I’m good to go. This makes it possible to have multiple, competing package managers. Or even multiple, co-operating package managers.
Part 5: Vote on the future
So, I think Lix has learnt from a lot of what has gone “right” in the NodeJS ecosystem, and built a great tool for the Haxe ecosystem. I love it, and will definitely be using it in my Haxe projects going forward.
The question is, do we really need “haxeshim” and “switchx” and other such tools just in order to have a competing package manager? For now sadly, because of the way haxe
and haxelib
are tied at the hip, you do need a hack like this. But there’s a discussion to change that. (See here and here).
If you care about Haxe projects having maintainable dependency management, you can help by voting up comments in a discussion that’s happening right now. Here are the comments that I think will help Haxe support something like Lix, and more competing package managers, as first class citizen going forward. Feel free to upvote with a thumbs up emoji:
https://github.com/HaxeFoundation/haxe-evolution/issues/30#issuecomment-333298948https://github.com/HaxeFoundation/haxe-evolution/issues/30#issuecomment-333299543https://github.com/HaxeFoundation/haxe-evolution/issues/30#issuecomment-333302625https://github.com/HaxeFoundation/haxe-evolution/issues/30#issuecomment-333311522https://github.com/HaxeFoundation/haxe-evolution/issues/30#issuecomment-333323976
And two of my own comments
https://github.com/HaxeFoundation/haxe-evolution/issues/30#issuecomment-333367950https://github.com/HaxeFoundation/haxe-evolution/issues/30#issuecomment-333368097
Feel free to have a look and contribute to the discussion. For now though – if you don’t mind installing haxeshim and switchx, there is a very good solution for managing your haxelibs and dependencies in a reliable, consistent, but still flexible way. And it’s called Lix.
5 replies on “Lix: A step forward in dependency management for Haxe projects”
This looks like a sweet combo of yarn and nvm, and moving from global-only dependencies to (per-project) vendored dependencies is a huge win – something I’ve wanted to be able to do (more easily) with haxelib for quite a while.
If I may, I’d suggest adding something to the wishlist/roadmap, it would be to support multiple workspaces within a project, like boltpkg or lerna from NodeJs land – github:boltpkg/bolt and github:lerna/lerna
Hey sorry, it seems like I missed some comments on my blog.
I haven’t used Lerna but obviously a few big projects do (Babel, Gatsby, more…) so it must have its uses. I imagine tink would be similar with all of its tiny libraries. You could always ask on https://gitter.im/lix-pm/Lobby or submit a feature request here https://github.com/lix-pm/lix.client/issues/new
[…] командой haxelib (также вас может заинтересовать lix — более продвинутый по сравнению с Haxelib менеджер […]
An even more important question to me is — does Haxelib or Lix avoid dependency hell when two dependencies depend on different major versions of a transitive dependency?
I asked about it in Haxelib here: https://github.com/HaxeFoundation/haxelib/issues/500
I think this is the most significant thing that Node’s module system gets right that many other languages (not just package managers but the language module systems themselves) get wrong.
For instance I’m pretty sure that if this happens with Java, you’re screwed. Maybe there’s some kind of classloader magic that can allow different classes to receive different source for the same import path, but I doubt Maven sets that up.
Also that’s great to hear that lix supports per-project dependencies just like npm does. I’ve had to deal with workarounds for global dependencies in Python (using pyenv) and it’s not pretty.