Skip to content

implement "nodeLinker": "isolated" in bun install#20440

Merged
Jarred-Sumner merged 76 commits intomainfrom
dylan/isolated-install
Jul 9, 2025
Merged

implement "nodeLinker": "isolated" in bun install#20440
Jarred-Sumner merged 76 commits intomainfrom
dylan/isolated-install

Conversation

@dylan-conway
Copy link
Member

@dylan-conway dylan-conway commented Jun 16, 2025

What does this PR do?

This PR implements the "nodeLinker": "isolated" option for bun install, providing a pnpm-style isolated installation mode that prevents phantom dependencies and and enables installing packages in parallel.

Key features:

  • Adds support for workspaces.nodeLinker: "isolated" configuration in package.json
  • Installs packages in an isolated structure under node_modules/.bun/ directory
  • Creates symlinks from node_modules/<package> to the actual packages in .bun/<package>@<version>/node_modules/<package>
  • Properly handles scoped packages, peer dependencies, and workspaces

Directory structure example:

node_modules/
├── my-dependency -> .bun/my-dependency@1.0.0/node_modules/my-dependency
└── .bun/
    ├── my-dependency@1.0.0/
    │   └── node_modules/
    │       ├── my-dependency/
    │       └── sub-dep -> ../../sub-dep@2.0.0/node_modules/sub-dep
    └── sub-dep@2.0.0/
        └── node_modules/
            └── sub-dep/

Configuration: (bunfig.toml)

[install]
linker = "isolated"

closes #4274
fixes #5688
closes #1760

How did you verify your code works?

Tests:

  • Basic single dependency installation
  • Scoped packages (e.g., @types/*)
  • Transitive dependencies
  • Cyclic dependencies
  • Folder dependencies
  • Workspace support
  • Symlink structure verification

TODO: many more test

@robobun
Copy link
Collaborator

robobun commented Jun 16, 2025

Updated 12:23 AM PT - Jul 9th, 2025

@dylan-conway, your commit b01a5f9 has 1 failures in Build #20008:


🧪   To try this PR locally:

bunx bun-pr 20440

That installs a local version of the PR into your bun-20440 executable, so you can run:

bun-20440 --bun

},
};
}
};
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
};

allocator.free(item.script);
}
}
// pub fn deinit(this: Package.Scripts.List, allocator: std.mem.Allocator) void {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you mean to comment this out

@compileError("unexpected path type");
}

const ret = std.c.link(src, dest);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ideally we would use the system version of link instead so it would use the linux syscall instead of the link c stdlib function but it's okay

@dylan-conway dylan-conway marked this pull request as ready for review July 9, 2025 06:20
@dylan-conway dylan-conway changed the title WIP: "nodeLinker": "isolated" implement "nodeLinker": "isolated" in bun install Jul 9, 2025
@Jarred-Sumner Jarred-Sumner merged commit f24e8cb into main Jul 9, 2025
50 of 53 checks passed
@Jarred-Sumner Jarred-Sumner deleted the dylan/isolated-install branch July 9, 2025 07:20
@Sparticuz
Copy link

Going to try this out on canary, does the workspaces.nodeLinker configuration go in the root package.json, or does it go in each app's package.json? "workspaces" is supposed to be an array of the workspaces, right?

@josefaidt
Copy link
Contributor

@Sparticuz I think it'll only go in your root package.json, and that workspaces key can be an object. You won't get autocomplete for this key but it'll work https://github.com/SchemaStore/schemastore/blob/master/src/schemas/json/package.json#L998

{
  "type": "module",
  "private": true,
  "workspaces": {
    "nodeLinker": "isolated",
    "packages": [
      "packages/*",
      "apps/*"
    ]
  },
  "devDependencies": {
    "@types/bun": "latest"
  },
  "peerDependencies": {
    "typescript": "^5"
  }
}

Just tested it out on my end with a bare-bones project 🎉

➜  ls -al node_modules/
total 0
drwxr-xr-x@  9 josef  staff   288 Jul 11 08:18 ./
drwxr-xr-x@ 10 josef  staff   320 Jul 11 08:18 ../
drwxr-xr-x@  4 josef  staff   128 Jul 11 08:18 .bin/
drwxr-xr-x@ 10 josef  staff   320 Jul 11 08:18 .bun/
drwxr-xr-x@  5 josef  staff   160 Jul 11 08:18 @types/
drwxr-xr-x@ 25 josef  staff   800 Jul 10 08:46 bun-types/
drwxr-xr-x@  7 josef  staff   224 Jul 10 08:46 csstype/
lrwxr-xr-x@  1 josef  staff    45 Jul 11 08:18 typescript@ -> .bun/typescript@5.8.3/node_modules/typescript
drwxr-xr-x@ 45 josef  staff  1440 Jul 10 08:46 undici-types/

for the bun team, for what it's worth npm has an "install-strategy" option they use for their experimental "linked" strategy that aims to produce a similar node_modules directory

https://docs.npmjs.com/cli/v11/commands/npm-install#install-strategy

I'd like to have this option available in bunfig.toml as well so I can configure this behavior globally

nektro pushed a commit that referenced this pull request Jul 12, 2025
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
@Sparticuz
Copy link

Sparticuz commented Jul 15, 2025

I've got a monorepo and am looking into bun support. Shouldn't @tokenizer/inflate, strtok3, token-types, and uint8array-extras be included in my node_modules folder since they are dependencies of file-types in this scenario?
package.json

{
  "name": "dependant-package",
  "version": "1.0.0",
  "description": "",
  "license": "ISC",
  "author": "",
  "type": "module",
  "main": "index.js",
  "dependencies": {
    "file-type": "^21.0.0"
  }
}

file-type's package.json

...
"dependencies": {
		"@tokenizer/inflate": "^0.2.7",
		"strtok3": "^10.2.2",
		"token-types": "^6.0.0",
		"uint8array-extras": "^1.4.0"
	},
	"devDependencies": {
		"@tokenizer/token": "^0.3.0",
		"@types/node": "^22.15.21",
...
monotest/packages/dependant-package
total 16K
drwxr-xr-x 3 kyle kyle 4.0K Jul 15 09:13 ./
drwxr-xr-x 3 kyle kyle 4.0K Jul 15 09:06 ../
drwxr-xr-x 2 kyle kyle 4.0K Jul 15 09:13 node_modules/
-rw-r--r-- 1 kyle kyle    0 Jul 15 09:11 index.js
-rw-r--r-- 1 kyle kyle  285 Jul 15 09:11 package.json

packages/dependant-package/node_modules
total 12K
drwxr-xr-x 2 kyle kyle 4.0K Jul 15 09:13 ./
drwxr-xr-x 3 kyle kyle 4.0K Jul 15 09:13 ../
lrwxrwxrwx 1 kyle kyle   66 Jul 15 09:13 file-type -> ../../../node_modules/.bun/file-type\@21.0.0/node_modules/file-type/

They are not in file-type's node_modules folder either.

dependant-package/node_modules/file-type
total 132K
drwxr-xr-x 2 kyle kyle 4.0K Jul 15 09:13 ./
drwxr-xr-x 4 kyle kyle 4.0K Jul 15 09:13 ../
-rw-r--r-- 2 kyle kyle  11K Jul 11 07:20 core.d.ts
-rw-r--r-- 2 kyle kyle  43K Jul 11 07:20 core.js
-rw-r--r-- 2 kyle kyle 4.9K Jul 11 07:20 index.d.ts
-rw-r--r-- 2 kyle kyle 2.4K Jul 11 07:20 index.js
-rw-r--r-- 2 kyle kyle 1.1K Jul 11 07:20 license
-rw-r--r-- 2 kyle kyle 3.6K Jul 11 07:20 package.json
-rw-r--r-- 2 kyle kyle  33K Jul 11 07:20 readme.md
-rw-r--r-- 2 kyle kyle 6.1K Jul 11 07:20 supported.js
-rw-r--r-- 2 kyle kyle 1.3K Jul 11 07:20 util.js

@dylan-conway
Copy link
Member Author

dylan-conway commented Jul 15, 2025

@Sparticuz transitive dependencies are symlinked to the the package location in ./node_modules/.bun. In this situation, the symlinks for @tokenizer/inflate, strtok3, and others will exist in ./node_modules/.bun/file-type@21.0.0/node_modules/*. And these dependencies will have their own entry in the store in the same way

Screenshot 2025-07-15 at 12 00 46 PM

Also note: we are going to change "workspaces.nodeLinker" to "linker" in bunfig.toml before releasing this feature in bun v1.2.19. It will also be able to read "node-linker" and "install-strategy" from .npmrc

@Sparticuz
Copy link

Sparticuz commented Jul 16, 2025

Hmm, ok, I guess what I'm looking for is 'isolated' + 'nohoist' or something similar that would link the transitive dependencies in a node_modules folder at the package level.

Currently, with just isolated, it seems like it (serverless framework) will not package strtok3, etc... because they are not linked within the packages node_modules folder anywhere. Let me know if this is a separate issue, or if this is covered under #20178

@dsabanin
Copy link

Really enjoying Bun these days, truly a breath of fresh air in the madness. Thanks for the great work everyone!

@branaway
Copy link

But it still does not resolve link dependencies like:

"dependencies": { "@slidev/client": "link:/Users/bran/localProjects/slidev/packages/client", // ...other dependencies }

Will this be implemented as well?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

7 participants