# Packages Installing packages is part of the build. You state a dependency, the project server resolves it, writes `node_modules`, and rebuilds against it, on the same loop that compiles your files and runs your tests. There is no separate installer to run and nothing to keep in sync by hand. ``` elements install lodash # add a package at its latest version elements install stripe@^14 # a range elements install dayjs@1.11.10 # a pin elements install @elements/app@local # a package you are building yourself elements install # install everything config.jsoc declares elements install -u # move each package to the newest its spec allows elements uninstall lodash ``` ## The model Dependencies are declared under `package.dependencies` in `config.jsoc`, and the versions they resolve to are written to `package.lock`. The CLI owns both: `elements install ` adds the spec to the config and the resolution to the lock in one step. You can also edit the dependency map by hand and save; the next build installs what changed. `package.lock` is generated. Do not edit it. ```jsoc package: { dependencies: { "@elements/app": "latest", "@elements/style": "latest", "stripe": "^14", }, }, ``` An existing resolution is reused from the lock, so a build stays on the versions it was tested against until you ask. `-u` upgrades within each spec. `-f` ignores the lock and resolves everything again, which can move packages you did not ask about, so reach for a version spec first. `-d` and `-p` install as dev and peer dependencies. An install that cannot resolve prints the reason and exits non-zero, and neither file is touched. ## Part of the build loop The installer runs through the same sequential build loop as everything else, so it is sequenced with file changes, type checks, and test runs. Two installs never conflict, and an install never interleaves with a half-finished build. Package sources enter the same source graph as your code: they are versioned, cached, tree-shaken, and hot reloaded like anything else, and every import path is resolved at build time to the exact file it will load at runtime. ## Fast The `node_modules` tree is computed in memory and diffed against what is on disk, so an install writes only the packages that actually changed. An install that changes nothing touches nothing. A package that is written is cloned into place, copy-on-write where the filesystem supports it, rather than extracted file by file. Registry manifests and tarballs are cached per machine under `~/elements/packages`, so a package you have installed once anywhere lands in the next project without a download. Resolution reads the manifest, not the tarball, so the only thing a fresh install waits on is the bytes it has not seen before. ## Node module compatible `node_modules` is a real tree laid out the way Node resolves modules, with nested copies where two packages need different versions of the same dependency. Anything that reads `node_modules` works, and every npm package installs as is. Packages come from the npm registry and from `packages.elements.dev`, which is where the Elements packages live. Elements emits CJS on both the server and the browser, so ESM-only packages and CJS-only packages both work everywhere; see `build` for why. ## Local packages A package you are developing alongside your app installs from a directory instead of a registry. Give it the version `local`: ``` elements install @elements/app@local ``` or write `"@elements/app": "local"` in `config.jsoc`. The package is found on the package search path: the directories in `ELEMENTS_PACKAGE_PATH` (a path list, `~` allowed), or `~/src` when that is unset, so `@elements/app` resolves to `~/src/@elements/app` by default. Set `ELEMENTS_PACKAGE_PATH` in `config/env/development.env` to point somewhere else. If the directory carries a build under `.elements/package/all` or `.elements/package/-`, that build is what installs; otherwise the directory itself does. A local package must live outside the project. Inside it, the project would be watching its own dependency as source, and an install could not tell the two apart. Local packages are watched. Save a file in the package and the project server copies the change into `node_modules` and rebuilds the app against it, the same as saving a file in the app. That is how you work on a package and the app that uses it at the same time, with one editor and no linking step. Flip the spec back to a registry version and the local copy is evicted on the next install; flip to `local` again and it is re-cloned cleanly. A deploy ships local packages to the machine and installs them there, so an app built on a package you have not published deploys the same as any other. ## Where things live - `config.jsoc`: the specs you declared. - `package.lock`: the resolutions, generated. - `node_modules/`: the installed tree, generated. Delete it and the next install rebuilds it in full. - `~/elements/packages/`: the per-machine cache of manifests and package sources, shared by every project. ## Related - `build`: the build loop that installs, compiles, migrates, and tests on save. - `cli`: every install flag. - `config`: `config.jsoc` and env files. - `deploy`: how packages, local ones included, reach a machine.