1.2.0 • Published 6 months ago

pnpm-hoist-layer v1.2.0

Weekly downloads
-
License
MIT
Repository
github
Last release
6 months ago

šŸŖ pnpm-hoist-layer

use .pnpmfile.cjs to hoist deps/devDeps to project like Nuxt Layer

Purpose

pnpm public-hoist-pattern only affects to the top-project (virtual store), not the sub-projects, therefore,

  • relative path issues may occur
  • copy same deps/devDeps from here to there
  • config too many hoist-pattern

how to auto add the dep's deps/devDeps to my project? i.e. give your deps/devDeps to me when i deps on you.

e.g. mobile->common, common->@nuxt, after hoist layer,

ā”œā”€ā”€ common
│   ā”œā”€ā”€ node_modules
│   │   ā”œā”€ā”€ @nuxt -> ../.pnpm/...
└── mobile
    ā”œā”€ā”€ node_modules
    │   ā”œā”€ā”€ @fessional
    │   │   └── razor-common -> ../.pnpm/...
+   │   ā”œā”€ā”€ @nuxt -> ../.pnpm/... // āœ… hoist as layer

Mechanism

When using catalog in pnpm-workspace.yaml, it is not easy to manage the dependencies via pnpm add, the recommended practice is to manually edit the catalog and the package.json, and then run pnpm i to install the update, at this point, the following happens to pnpm-hoist-layer.

  • make a temporary directory(tmpDir), and write the .pnpmfile.cjs hook.
  • start the sub-process, pnpm -r i --resolution-only --lockfile-dir=tmpDir
  • sub-process quickly resolves packages related to hoistLayer
  • top-process parse stdout of sub-process as hoistLayer metadata
  • top-process merges hoistLayer metadata via hooks

the hoistLayer metadata is šŸ“ hoist-layer.json in the console,

[
  {
    "name": "hoist1",
    "dependencies": {
      "date-fns": "catalog:h1",
      "lodash-es": "catalog:h1"
    },
    "devDependencies": {}
  },
  {
    "name": "hoist2",
    "dependencies": {
      "date-fns": "catalog:h2",
      "hoist1": "workspace:*",
      "lodash-es": "catalog:h1"
    },
    "devDependencies": {},
    "hoistLayer": [
      "hoist1"
    ]
  }
]

Usage

(1) add layer to the package.json

  • hoistLayer - to define which is the layer
  • *dependencies - for package resolution
  "devDependencies": {
+   "@fessional/razor-common": "file:../common",
  },
+ "hoistLayer": [
+   "@fessional/razor-common",
+ ]

(2) write .pnpmfile.cjs to hook

## šŸ’¾ opt-1: project install and require
pnpm add -D pnpm-hoist-layer
cat > .pnpmfile.cjs << 'EOF'
module.exports = require('pnpm-hoist-layer');
EOF

## šŸ“¦ opt-2: write content to .pnpmfile.cjs
curl -o .pnpmfile.cjs https://raw.githubusercontent.com/trydofor\
/pnpm-hoist-layer/main/index.js

## šŸ  opt-3: global install and require
pnpm add -g pnpm-hoist-layer
cat > .pnpmfile.cjs << 'EOF'
module.exports = (() => {
  try {
    return require('pnpm-hoist-layer');
  }
  catch {
    const gr = require('child_process').execSync('pnpm root -g').toString().trim();
    return require(require('path').join(gr, 'pnpm-hoist-layer'));
  }
})();
EOF

Known Issues

the deps tree are resolved from top to bottom, and hoist from bottom to top, it's a reverse process.

  • āœ… shared-workspace-lockfile=false, may šŸž peers
  • āœ… monorepo + shared-workspace-lockfile=false, but šŸž default=true
  • āœ… pnpm cli at top-dir, but šŸž sub-dir (packages/*)
  • āœ… --resolution-only resolve devDependencies, but ā— pnpm i NOT.
  • ā— do NOT use link:, it do NOT hook
  • ā— do NOT deps indirectly , 2+ level deps NOT resolved
  • ā— this is a bad practice

Useful Commands

## init workspace top-project first
pnpm -w i --ignore-pnpmfile
## init workspace sub-project
pnpm -r i
## to debug with env DEBUG != null
DEBUG=1 pnpm i
## ignore if error
pnpm i --ignore-pnpmfile --ignore-scripts

## asdf nodejs+pnpm, should disable corepack
export PNPM_HOME="$(asdf where pnpm)"
pnpm -g add pnpm-hoist-layer

## only corepack
corepack enable pnpm
corepack use pnpm@latest

Test and Diff

Glossary
ā”œā”€ā”€ multi pkg, one git (often called "monorepo")
│   ā”œā”€ā”€ with workspace (termed as "mono")
│   └── without workspace (termed as "poly")
└── one pkg, one git (termed as "solo")
node -v #v20.16.0
pnpm -v #9.12.1

pnpm test
# āœ… Success mono1, npmrc={}
# āœ… Success mono1, npmrc={"shared-workspace-lockfile":false}
# āœ… Success mono2, npmrc={}
# āœ… Success mono2, npmrc={"shared-workspace-lockfile":false}
# āœ… Success poly1, npmrc={}
# āœ… Success poly2, npmrc={}
# āœ… Success hoist, npmrc={}
  • hoist - hoist auto/manual testing
  • mono1 - multi-pkg + workspace, sub hoistLayer
  • mono2 - multi-pkg + workspace, top hoistLayer
  • poly1 - multi-pkg, sub hoistLayer
  • poly2 - multi-pkg, top hoistLayer
  • solo - single pkg as deps for test

Mono before and after

diff mono from pnpm -r i --ignore-pnpmfile to pnpm -r i like this,

## pnpm -r list
Legend: production dependency, optional only, dev only
mono-test-0@1.0.0 pnpm-hoist-layer/test/mono/packages/pkg0
+ dependencies:
+   solo-prd-dep link:../../solo/prd
= devDependencies:
=   mono-test-1 link:../pkg1
+   mono-test-2 link:../pkg2
+   solo-dev-dep link:../../solo/dev
mono-test-1@1.0.0 pnpm-hoist-layer/test/mono/packages/pkg1
+ dependencies:
+   solo-prd-dep link:../../solo/prd
= devDependencies:
=   mono-test-2 link:../pkg2
+   solo-dev-dep link:../../solo/dev
mono-test-2@1.0.0 pnpm-hoist-layer/test/mono/packages/pkg2
= dependencies:
=   solo-prd-dep link:../../solo/prd
= devDependencies:
=   solo-dev-dep link:../../solo/dev

## tree -L 4
āœ… mono
= ā”œā”€ā”€ node_modules
= ā”œā”€ā”€ package.json
= ā”œā”€ā”€ packages
= │   ā”œā”€ā”€ pkg0
= │   │   ā”œā”€ā”€ node_modules
= │   │   │   ā”œā”€ā”€ mono-test-1 -> ../../../node_modules/.pnpm/
+ │   │   │   └── mono-test-2 -> ../../pkg2
+ │   │   │   ā”œā”€ā”€ solo-dev-dep -> ../../../solo/dev
+ │   │   │   ā”œā”€ā”€ solo-prd-dep -> ../../../solo/prd
= │   │   └── package.json
= │   ā”œā”€ā”€ pkg1
= │   │   ā”œā”€ā”€ node_modules
= │   │   │   └── mono-test-2 -> ../../pkg2
+ │   │   │   ā”œā”€ā”€ solo-dev-dep -> ../../../solo/dev
+ │   │   │   ā”œā”€ā”€ solo-prd-dep -> ../../../solo/prd
= │   │   └── package.json
= │   └── pkg2
= │       ā”œā”€ā”€ node_modules
= │       │   └── solo-dev-dep -> ../../../solo/dev
= │       │   ā”œā”€ā”€ solo-prd-dep -> ../../../solo/prd
= │       └── package.json
= ā”œā”€ā”€ pnpm-lock.yaml
= └── pnpm-workspace.yaml

Poly before and after

diff poly from pnpm -r i --ignore-pnpmfile to pnpm -r i like this,

## pnpm -r list
Legend: production dependency, optional only, dev only
poly-test-0@1.0.0 pnpm-hoist-layer/test/poly/packages/pkg0
+ dependencies:
+   solo-prd-dep link:../../solo/prd
= devDependencies:
=   poly-test-1 file:../pkg1
+   poly-test-2 file:../pkg2
+   solo-dev-dep link:../../solo/dev
poly-test-1@1.0.0 pnpm-hoist-layer/test/poly/packages/pkg1
+ dependencies:
+   solo-prd-dep link:../../solo/prd
= devDependencies:
=   poly-test-2 file:../pkg2
+   solo-dev-dep link:../../solo/dev
poly-test-2@1.0.0 pnpm-hoist-layer/test/poly/packages/pkg2
= dependencies:
=   solo-prd-dep link:../../solo/prd
= devDependencies:
=   solo-dev-dep link:../../solo/dev

## tree -L 4
āœ… poly
= ā”œā”€ā”€ package.json
= ā”œā”€ā”€ packages
= │   ā”œā”€ā”€ pkg0
= │   │   ā”œā”€ā”€ node_modules
= │   │   │   ā”œā”€ā”€ poly-test-1 -> .pnpm/
+ │   │   │   └── poly-test-2 -> .pnpm/
+ │   │   │   ā”œā”€ā”€ solo-dev-dep -> ../../../solo/dev
+ │   │   │   ā”œā”€ā”€ solo-prd-dep -> ../../../solo/prd
= │   │   ā”œā”€ā”€ package.json
= │   │   └── pnpm-lock.yaml
= │   ā”œā”€ā”€ pkg1
= │   │   ā”œā”€ā”€ node_modules
= │   │   │   └── poly-test-2 -> .pnpm/
+ │   │   │   ā”œā”€ā”€ solo-dev-dep -> ../../../solo/dev
+ │   │   │   ā”œā”€ā”€ solo-prd-dep -> ../../../solo/prd
= │   │   ā”œā”€ā”€ package.json
= │   │   └── pnpm-lock.yaml
= │   └── pkg2
= │       ā”œā”€ā”€ node_modules
= │       │   ā”œā”€ā”€ solo-dev-dep -> ../../../solo/dev
= │       │   └── solo-prd-dep -> ../../../solo/prd
= │       ā”œā”€ā”€ package.json
= │       └── pnpm-lock.yaml
= └── pnpm-lock.yaml
1.2.0

6 months ago

1.1.9

9 months ago

1.1.8

9 months ago

1.1.7

9 months ago

1.1.6

9 months ago

1.1.5

10 months ago

1.1.4

10 months ago

1.1.3

10 months ago

1.1.2

10 months ago

1.1.1

10 months ago

1.1.0

10 months ago

1.0.3

10 months ago

1.0.2

10 months ago

1.0.1

10 months ago

1.0.0

10 months ago

0.1.1

10 months ago

0.1.0

10 months ago