floco
is a Node.js package management and build tool powered by
Nix.
floco
is a bold departure from conventional Node.js package management tooling
focusing on reproducibility, distributed caching, and sandboxing.
Every package is built in a strictly declared sandbox isolated from the runtime
system, much like an OCI container.
This approach allows packages to be built once and reused on any system that
shares the same architecture and platform.
Artifacts are cached locally and may be distributed among a cluster of systems
allowing development environments to be created at more than twice the speed of
npm
or yarn
.
Underlying installers are implemented in bash
and limit themselves to using
coreutils
, findutils
, and jq
improving reproducibility and readability
compared to tooling implemented using a sprawling maze of JavaScript.
Despite this repository’s seemingly small form, it is the result of nearly
a year of exploration, trial, and refinement.
A great deal of effort was expended to make this piece of software
suck less than the competition, but rest assured that these routines were
not implemented naively.
Writing a JavaScript package management framework in bash
and nix
was
done not because it is easy - on the contrary it was fucking hard.
There’s a dedicated getting started guide that is the best place to dive in.
The summary is: Generate a config, copy a boilerplate template, and you’re set.
I haven’t created nix flake init -t <TEMPLATE>;
outputs yet, so you gotta
go read the guide for now, but those are coming soon.
floco
uses a small collection of bash
scripts to perform install tasks
and drive builds.
These scripts do not depend on Nix, and are suitable for standalone use
as replacements for pacote extract
( install-module.sh
) and
(npm|yarn) run
( run-script.sh
).
These routines can be found in the <floco>/setup/
subdir, named after the
setup-hooks
pattern found in nixpkgs.
Package metadata collection, also called translation, and project composition is managed using Nixpkgs Modules similar to those used by NixOS, dream2nix, or home-manager.
These modules are organized as sets of interface.nix
and
implementation.nix
files and are designed to be extensible.
The core of the module system revolves around a record called pdef
, short
for “package definition”, which organizes translated metadata, and
package
records which organize the build pipelines.
This separation simplifies the organization of the translation and builder APIs, but the rationale runs further. The split allows us to flatly state: build routines must never perform impure operations, and translation routines must only produce fields that can be serialized to JSON.
Serialization of translated metadata allows Nix’s flake
features to
drastically improve performance by leveraging
eval caching to avoid
re-evaluation of recipe generation on successive runs.
The pdef
record closely mirrors the pseudo-standard schema used by most
package.json
files; but is much stricter about how declarations
are written.
If desired, users could ditch package.json
files altogether and simply
write pdef
records for their projects.
At time of writing only a few translators have been migrated from the beta iteration, at-node-nix, but in the near future these will be finalized for production use.
This is our bread and butter, and serves as the default implementation for
creating a pdef
record.
On its own this translator would require users to explicitly declare the
structure of their node_modules/
tree using the treeInfo
submodule.
For this reason we strongly recommend using the package-lock.json
translator for projects with large dependency graphs.
The term ideal tree refers to the mapping of a node_modules/
tree
from a dependency graph.
This process is by far the most complex and challenging aspect of
Node.js package management.
While floco
currently relies on npm
to generate ideal trees, this
is expected to end soon.
The beta repository
at-node-nix contains a
large body of routines to perform best effort treeInfo
mapping, specifically handling projects which only require a single
version of any package ( this property is called The Golden Rule in
package management contexts ).
Additionally the semver resolution routines used to fetch closures of packument records effectively solve half of the ideal tree process, leaving only scope and follows management to be completed.
This is by far the most developed translator, and is the recommended option for large projects.
This translator will automatically fill treeInfo
submodules, and
triggers minimal network fetching.
Many of the following extensions have function drafts or well tested prototypes in the beta release of floco; but are not developed enough for use in production code-bases as pieces of reliable infrastructure.
- Improved support for package.json workspaces.
- Currently reliance on npm and special configuration based on in depth knowledge of floco is necessary to accomplish workspace support.
- Practically a template or example using workspaces is likely sufficient for the immediate future; but the NixOS Module system is expected to resolve issues that previously made workspaces complex to manage.
- Expanded CLI tooling.
- Currently users are asked to interact with nix to drive builds, tests,
update metadata, etc.
Ideally a simple bash script would provide familiar commands such as
floco add <PKG>
,floco publish
,floco update
,floco build
, etc thatnpm
andyarn
users are already familiar with.
- Currently users are asked to interact with nix to drive builds, tests,
update metadata, etc.
Ideally a simple bash script would provide familiar commands such as
- Improved project composition structures.
- Currently a rudimentary API for composing projects exists for defining,
consuming, and modifying package definitions across multiple repositories;
and while it does an incredible job of hiding complexity it is not well
documented, and the migration to
Nixpkgs
Modules will necessitate small changes to the existing implementations of these APIs.
- Currently a rudimentary API for composing projects exists for defining,
consuming, and modifying package definitions across multiple repositories;
and while it does an incredible job of hiding complexity it is not well
documented, and the migration to
- Nix plugin to read/write caches globally and into
flake.lock
.- This is the real end goal for
floco
. It should be possible to read/writefloco
metadata toflake.lock
and existingnix
caches. - There is currently a draft plugin which allows nix to adopt npm URIs to
refer to packages as
lodash@4.17.21
which could be expanded upon. - Project templates and propagation of build recipes could allow
nix
to abstract away the generation offlake.nix
for the vast majority of projects which would be a significant UX breakthrough.
- This is the real end goal for
yarn.lock
translators.- Development of
yarn
translators was dropped after the creation of the first working prototype in favor ofpackage-lock.json
translation. There is a large collection of existing routines that can translateyarn.lock
tonix
in at-node-nix, but not using thefloco
metadata schema, and not in a coherent or documented flow.
- Development of
- Semantic version parsing, and ideal tree formation.
- Currently
floco
really relies onnpm
and itspackage-lock.json
to construct non-trivial node_module/ metadata declarations. This reliance is a major pain point for handling projects which currently use yarn since interoperability betweenyarn
andnpm
across their associated lockfiles is implemented incredibly poorly, to such a degree that you cannot trust them to behave predictably in the same source tree. - Semver parsing and solving SAT is implemented in the beta repository, and has been testing on large non-trivial inputs quite successfully. Still this effort requires a few weeks of polishing to really approve for use in production.
- Construction of ideal tree from semver SAT is a project in and of itself
in order to support things like
optionDependencies
,peerDependencies
,bundledDependencies
, and other oddballs which are a prerequisite for use in the general case.
- Currently