1.2.0 • Published 1 year ago

pnpm-hoist-layer v1.2.0

Weekly downloads
-
License
MIT
Repository
github
Last release
1 year 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

1 year ago

1.1.9

1 year ago

1.1.8

1 year ago

1.1.7

1 year ago

1.1.6

1 year ago

1.1.5

1 year ago

1.1.4

1 year ago

1.1.3

1 year ago

1.1.2

1 year ago

1.1.1

1 year ago

1.1.0

1 year ago

1.0.3

1 year ago

1.0.2

1 year ago

1.0.1

1 year ago

1.0.0

1 year ago

0.1.1

1 year ago

0.1.0

1 year ago