mirror of
https://github.com/bigskysoftware/htmx.git
synced 2025-10-02 07:21:05 +00:00
220 lines
13 KiB
Markdown
220 lines
13 KiB
Markdown
+++
|
||
title = "Vendoring"
|
||
description = """\
|
||
Carson Gross explores the concept of 'vendoring' in software development, where external project sources are copied \
|
||
directly into a project. He covers the benefits of vendoring, such as improved visibility and control over \
|
||
dependencies, and discusses challenges like transitive dependencies and the culture of dependency in modern software \
|
||
development. He also contrasts vendoring with modern dependency management tools, and considers the potential for \
|
||
vendor-first dependency managers to combine the strengths of both approaches. He encourages a rethinking of \
|
||
dependencies and promotes a more independent approach to software development."""
|
||
date = 2025-01-27
|
||
updated = 2025-01-27
|
||
authors = ["Carson Gross"]
|
||
[taxonomies]
|
||
tag = ["posts"]
|
||
+++
|
||
|
||
"Vendoring" software is a technique where you copy the source of another project directly into your own project.
|
||
|
||
It is an old technique that has been used for time immemorial in software development, but the term "vendoring" to
|
||
describe it appears to have originated in the [ruby community](https://stackoverflow.com/posts/72115282/revisions).
|
||
|
||
Vendoring can be and is still used today. You can vendor htmx, for example, quite easily.
|
||
|
||
Assuming you have a `/js/vendor` directory in your project, you can just download the source into your own project like
|
||
so:
|
||
|
||
```bash
|
||
curl https://raw.githubusercontent.com/bigskysoftware/htmx/refs/tags/v2.0.4/dist/htmx.min.js > /js/vendor/htmx-2.0.4.min.js
|
||
```
|
||
|
||
You then include the library in your `head` tag:
|
||
|
||
```html
|
||
<script src="/js/vendor/htmx-2.0.4.min.js"></script>
|
||
```
|
||
|
||
And then you check the htmx source into your own source control repository. (I would even recommend considering using
|
||
the [non-minimized version](https://raw.githubusercontent.com/bigskysoftware/htmx/refs/tags/v2.0.4/dist/htmx.js), so
|
||
you can better understand and debug the code.)
|
||
|
||
That's it, that's vendoring.
|
||
|
||
## Vendoring Strengths
|
||
|
||
OK, great, so what are some strengths of vendoring libraries like this?
|
||
|
||
It turns out there are quite a few:
|
||
|
||
* Your entire project is checked in to your source repository, so no external systems beyond your source control need
|
||
to be involved when building it
|
||
* Vendoring dramatically improves dependency *visibility*: you can _see_ all the code your project depends on, so you
|
||
won't have a situation like we have in htmx, where we feel like we only have a few development dependencies, whe in
|
||
fact we may have a lot
|
||
* This also means if you have a good debugger, you can step into the library code as easily as any other code. You
|
||
can also read it, learn from it and even modify it if necessary.
|
||
* From a security perspective, you aren't relying on opaque code. Even if your package manager has
|
||
an integrity hash system, the actual code may be opaque to you. With vendored code it is checked in and can be
|
||
analysed automatically or by a security team.
|
||
* Personally, it has always seemed crazy to me that people will often resolve dependencies at deployment time, right
|
||
when your software is about to go out the door. If that bothers you, like it does me, vendoring puts a stop to it.
|
||
|
||
On the other hand, vendoring also has one massive drawback: there typically isn't a good way to deal with what is called
|
||
the [transitive dependency](https://en.wikipedia.org/wiki/Transitive_closure) problem.
|
||
|
||
If htmx had sub-dependencies, that is, other libraries that it depended on, then to vendor it properly you would have to
|
||
start vendoring all those libraries as well. And if those dependencies had further dependencies, you'd need to install
|
||
them as well... And on and on.
|
||
|
||
Worse, two dependencies might depend on the same library, and you'll need to make sure you get the
|
||
[correct version](https://en.wikipedia.org/wiki/Dependency_hell) of that library for everything to work.
|
||
|
||
This can get pretty difficult to deal with, but I want to make a paradoxical claim that this weakness (and, again, it's
|
||
a real one) is actually a strength in some way:
|
||
|
||
Because dealing with large numbers of dependencies is difficult, vendoring encourages a culture of _independence_.
|
||
|
||
You get more of what you make easy, and if you make dependencies easy, you get more of them. Making dependencies,
|
||
_especially_ transitive dependencies, more difficult would make them less common.
|
||
|
||
And, as we will see in a bit, maybe fewer dependencies isn't such a bad thing.
|
||
|
||
## Dependency Managers
|
||
|
||
That's great and all, but there are [significant](https://gist.github.com/datagrok/8577287)
|
||
[drawbacks](https://web.archive.org/web/20180216205752/http://blog.bithound.io/why-we-stopped-vendoring-our-npm-dependencies/)
|
||
to vendoring, particular the transitive dependency problem.
|
||
|
||
Modern software engineering uses dependency managers to deal with the dependencies of software projects. These tools
|
||
allow you to specify your projects dependencies, typically via some sort of file. They then they will install those
|
||
dependencies and resolve and manage all the other dependencies that are necessary for those dependencies to work.
|
||
|
||
One of the most widely used package managers is NPM: The [Node Package Manager](https://www.npmjs.com/). Despite having
|
||
no runtime dependencies, htmx uses NPM to specify 16 development dependencies. Development dependencies are dependencies
|
||
that are necessary for development of htmx, but not for running it. You can see the dependencies at the bottom of
|
||
the NPM [`package.json`](https://github.com/bigskysoftware/htmx/blob/master/package.json) file for the project.
|
||
|
||
Dependency managers are a crucial part of modern software development and many developers today couldn't imagine
|
||
writing software without them.
|
||
|
||
### The Trouble with Dependency Managers
|
||
|
||
So dependency managers solve the transitive dependency problem that vendoring has. But, as with everything in software
|
||
engineering, there are tradeoffs associated with them. To see some of these tradeoffs, let's take a look at the
|
||
[`package-lock.json`](https://github.com/bigskysoftware/htmx/blob/master/package-lock.json) file in htmx.
|
||
|
||
NPM generates a `package-lock.json` file that contains the resolved transitive closure of dependencies for a project, with
|
||
the concrete versions of those dependencies. This helps ensure that the same dependencies are used unless a user
|
||
explicitly updates them.
|
||
|
||
If you take a look at the `package-lock.json` for htmx, you will find that the original 13 development dependencies have
|
||
ballooned into a total of 411 dependencies when all is said and done.
|
||
|
||
htmx, it turns out, relies on a huge number of packages, despite priding itself on being a relatively lean. In fact,
|
||
the `node_modules` folder in htmx is a whopping 110 megabytes!
|
||
|
||
But, beyond this bloat there are deeper problems lurking in that mass of dependencies.
|
||
|
||
While writing this essay I found that htmx apparently depends on the
|
||
[`array.prototype.findlastindex`](https://www.npmjs.com/package/array.prototype.findlastindex), a
|
||
[polyfill](https://en.wikipedia.org/wiki/Polyfill_(programming)) for a JavaScript feature introduced in
|
||
[2022](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findLastIndex).
|
||
|
||
Now, [htmx 1.x](https://v1.htmx.org/) is IE compatible, and I don't *want* polyfills for _anything_: I want to write
|
||
code that will work in IE without any additional library support. And yet a polyfill has snuck in via a chain
|
||
of dependencies (htmx does not directly rely on it) that introduces a dangerous polyfill that would let me write
|
||
code that would break in IE, as well as other older browsers.
|
||
|
||
This polyfill may or may not be available when I run the htmx [test suite](https://htmx.org/test/) (it's hard to tell)
|
||
but that's the point: some dangerous code has snuck into my project without me even knowing it, due to the number
|
||
and complexity of the (development) dependencies it has.
|
||
|
||
This demonstrates significant _cultural_ problem with dependency managers:
|
||
|
||
They tend to foster a culture of, well, dependency.
|
||
|
||
A spectacular example of this was the infamous [left-pad incident](https://en.wikipedia.org/wiki/Npm_left-pad_incident),
|
||
in which an engineer took down a widely used package and broke the build at companies like Facebook, PayPal, Netflix,
|
||
etc.
|
||
|
||
That was a relatively innocuous, although splashy, issue, but a more serious concern is
|
||
[supply chain attacks](https://en.wikipedia.org/wiki/Supply_chain_attack), where a hostile entity is able to compromise
|
||
a company via code injected unwittingly via dependencies.
|
||
|
||
The larger our dependency graph gets, the worse these problems get.
|
||
|
||
## Dependencies Reconsidered
|
||
|
||
I'm not the only person thinking about our culture of dependency. Here's what some other, smarter folks have to say
|
||
about it:
|
||
|
||
[Armin Ronacher](https://x.com/mitsuhiko), creator of [flask](https://flask.palletsprojects.com/en/stable/)
|
||
recently said this on [the ol'twits](https://x.com/mitsuhiko/status/1882739157120041156):
|
||
|
||
> The more I build software, the more I despise dependencies. I greatly prefer people copy/pasting stuff into their own
|
||
> code bases or re-implement it. Unfortunately the vibe of the time does not embrace that idea much. I need that vibe
|
||
> shift.
|
||
|
||
He also wrote a great blog post about his
|
||
[experience with package management](https://lucumr.pocoo.org/2025/1/24/build-it-yourself/) in the Rust ecosystem:
|
||
|
||
> It's time to have a new perspective: we should give kudos to engineers who write a small function themselves instead
|
||
> of hooking in a transitive web of crates. We should be suspicious of big crate graphs. Celebrated are the minimal
|
||
> dependencies, the humble function that just quietly does the job, the code that doesn't need to be touched for years
|
||
> because it was done right once.
|
||
|
||
Please go read it in full.
|
||
|
||
Back in 2021, [Tom Macwright](https://macwright.com) wrote this in
|
||
[Vendor by default](https://macwright.com/2021/03/11/vendor-by-default)
|
||
|
||
> But one thing that I do think is sort of unusual is: I’m vendoring a lot of stuff.
|
||
>
|
||
> Vendoring, in the programming sense, means “copying the source code of another project into your project.” It’s in
|
||
> contrast to the practice of using dependencies, which would be adding another project’s name to your package.json
|
||
> file and having npm or yarn download and link it up for you.
|
||
|
||
I highly recommend reading his take on vendoring as well.
|
||
|
||
## Software Designed To Be Vendored
|
||
|
||
Some good news, if you are an open source developer and like the idea of vendoring, is that there is a simple way to
|
||
make your software vendor-friendly: remove as many dependencies as you can.
|
||
|
||
[DaisyUI](https://daisyui.com/), for example, has been in the process of
|
||
[removing their dependencies](https://x.com/Saadeghi/status/1882556881253826941), going from 100 dependencies in
|
||
version 3 to 0 in version 5.
|
||
|
||
There is also a set htmx-adjacent projects that are taking vendoring seriously:
|
||
|
||
* [Surreal](https://github.com/gnat/surreal) - a lightweight jQuery alternative
|
||
* [Facet](https://github.com/kgscialdone/facet) - an HTML-oriented Web Component library
|
||
* [fixi](https://github.com/bigskysoftware/fixi) - a minimal htmx alternative
|
||
|
||
None of these JavaScript projects are available in NPM, and all of them [recommend](https://github.com/gnat/surreal#-install)
|
||
[vendoring](https://github.com/kgscialdone/facet#installation) the [software](https://github.com/bigskysoftware/fixi#installing)
|
||
into your own project as the primary installation mechanism.
|
||
|
||
## Vendor First Dependency Managers?
|
||
|
||
The last thing I want to briefly mention is a technology that combines both vendoring and dependency management:
|
||
vendor-first dependency managers. I have never worked with one before, but I have been pointed to
|
||
[vend](https://github.com/fosskers/vend), a common lisp vendor oriented package manager (with a great README), as well
|
||
as [go's vendoring option](https://go.dev/ref/mod#vendoring).
|
||
|
||
In writing this essay, I also came across [vendorpull](https://github.com/sourcemeta/vendorpull) and
|
||
[git-vendor](https://github.com/brettlangdon/git-vendor), both of which are small but interesting projects.
|
||
|
||
These all look like excellent tools, and it seems to me that there is an opportunity for some of them (and tools like
|
||
them) to add additional functionality to address the traditional weaknesses of vendoring, for example:
|
||
|
||
* Managing transitive dependencies, if any
|
||
* Relatively easy updates of those dependencies
|
||
* Managing local modifications made to dependencies (and maybe help manage contributing them upstream?)
|
||
|
||
With these additional features I wonder if vendor-first dependency managers could compete with "normal" dependency
|
||
managers in modern software development, perhaps combining some of the benefits of both approaches.
|
||
|
||
Regardless, I hope that this essay has helped you think a bit more about dependencies and perhaps planted the idea that
|
||
maybe your software could be a little less, well, dependent on dependencies.
|