主要是把pnpm的依赖迁移到内网,因为pnpm项目是软硬连接的原因就不能像npm项目一样直接通过复制node_modules的方式去内网使用,所以操作起来比较麻烦。
如果大家知道有什么改进的地方或更好的方式请告知谢谢!
一共两种方式根据自身情况选择:
方式一:操作简单,适合迁移单个pnpm项目,但不方便更新和添加依赖,多人团队的话需要每个人的电脑都要重复操作一次全部步骤。
方式二:操作复杂,适合迁移多个pnpm项目,方便以后的依赖更新和添加,多人团队只需要第一个人上传好依赖,其他人连接私库只需pnpm i即可。
方式一:复制外网的本地依赖到内网
1.在外网环境的电脑用pnpm i 把项目代码的依赖都安装好。
默认是连接官网registry.npmjs.org,我是连接本地localhost:8081的nexus私库下载依赖的,连接哪个源地址都可以。
2.把外网电脑的本地依赖缓存 pnpm-cache文件复制到内网电脑。
默认路径C:\Users\AppData\Local\pnpm-cache
查看路径的cmd命令 npm get cache
查看pnpm-cache/metadata/里有对应registry地址名称的文件夹,这些文件夹存放着各个仓库的依赖,如图就有我本地localhost:8081的nexus私库的依赖,文件夹名称就是仓库源地址(默认还有个http://registry.npmjs.org或http://registry.npmmirror.com的文件夹),里面就放着以前代码执行pnpm i后的依赖缓存。
3.把外网电脑的.pnpm-store文件复制到内网电脑。
位置在代码所在盘的根目录下 例如 D:\.pnpm-store ,是一个隐藏文件。
.pnpm-store文件存放着依赖的硬链接,硬链接就指向pnpm-cache文件里的依赖
4.把外网电脑项目代码里的node_moduels文件删除,并压缩整个项目代码文件夹(必须含有pnpm-lock.yaml文件)复制到内网电脑。
5.在内网电脑解压项目代码文件并设置内网项目代码的pnpm的源地址为pnpm-cache/metadata/里对应依赖缓存文件夹名称的地址。
因为我内网本地的pnpm-cache/metadata/里有一个名为localhost+8081的依赖缓存文件夹所以
以我的缓存为例子,在代码目录里执行:pnpm set registry http://localhost:8081/repository/npm-hosted/
你在外网的pnpm项目用哪个源地址pnpm install的,内网就设置相同的源地址。
6.最后一步在代码里执行pnpm install --offline ,此时会去本地缓存获取依赖来安装,下载完成后执行pnpm run dev运行代码。大功告成!
方式二:连接内网nexus私库下载依赖
此方法前提先有一个本地nexus私库,没有的自行上网搜索如何安装使用nexus上传npm依赖,比较简单。先在本地nexus验证此方法二没问题了再去内网nexus重复一次即可。
1.根据pnpm-lock.yaml文件生成package-lock.json文件。
因为pnpm不能像npm一样使用这个命令来生成lock文件:npm install --package-lock-only ,
所以需要安装一个工具来生成:npm i -g pnpm-lock-to-npm-lock。
但是pnpm-lock-to-npm-lock是有问题的不能直接用,要先修改里面的源码。
要先找到这个工具安装的路径再打开源码修改,因为我用了nvm所以我的安装路径是C:\Users\10368\AppData\Roaming\nvm\v16.20.2\node_modules\pnpm-lock-to-npm-lock,如果没用nvm路径应该是\Roaming\npm\ node_modules里。
修改这个js文件pnpm-lock-to-npm-lock\lib\createLock.js ,把原来的createLock方法整个替换成下面的代码。
function createLock(pnpmLock) {
var pnpmLockObject = (0, yaml_1.parse)(pnpmLock);
// Convert pnpm-lock object to npm package-lock object
var npmLockObject = {
name: "Lockfile generated with Virtru pnpm-lock-to-npm-lock tool",
version: "1.0.0",
lockfileVersion: 2,
requires: true,
packages: {},
dependencies: {},
};
Object.entries(pnpmLockObject.packages).forEach(function (_a) {
var _b, _c;
var _d;
var packageName = _a[0],
lockObj = _a[1];
// @vue/test-utils@2.4.4(vue@3.4.15):
var pkgName = "";
var version = "";
var scopedPkgName = "";
var formatter = function (packageName) {
var atArr = packageName.split("@");
var atCount = atArr.length - 1;
// 只有一个 @
if (atCount === 1) {
// jest@27.5.1
version = atArr[1];
pkgName = atArr[0];
scopedPkgName = pkgName;
} else {
// 多个@
// @vue/test-utils@2.4.4(vue@3.4.15) 或 jest@27.5.1
if (packageName.startsWith("@")) {
if (atArr[2].indexOf("(") != -1) {
// 最后面有(vue@3.4.15)这种后缀的
// @vue/test-utils@2.4.4(vue@3.4.15)
version = atArr[2].split("(")[0];
scopedPkgName = "@" + atArr[1];
pkgName = atArr[1].split("/")[1];
} else {
// @vue/shared@3.4.15
version = atArr[2];
scopedPkgName = "@" + atArr[1];
pkgName = atArr[1].split("/")[1];
}
} else {
// tsup@5.12.1(typescript@4.9.5)
version = atArr[1].split("(")[0];
pkgName = atArr[0];
scopedPkgName = pkgName;
}
}
};
if (lockObj.name) {
scopedPkgName = lockObj.name;
version = lockObj.version;
if (scopedPkgName.startsWith("@")) {
pkgName = scopedPkgName.substring(pkgName.indexOf("/") + 1);
} else {
pkgName = scopedPkgName;
}
} else {
if (packageName.startsWith("/")) {
// packageName= /@vue/test-utils@2.4.4(vue@3.4.15) 或 /jest@27.5.1 或 /@vue/shared@3.4.15
packageName = packageName.substring(1);
// packageName = @vue/test-utils@2.4.4(vue@3.4.15) 或 jest@27.5.1
formatter(packageName);
}
// else {
// // packageName= registry.npmmirror.com/@vue/test-utils/2.4.3_vue@3.4.15 或 registry.npmmirror.com/ignore/5.3.0:
// pkgName = packageName.substring(packageName.indexOf("/") + 1);
// // pkgName = @vue/test-utils/2.4.3_vue@3.4.15 或 ignore/5.3.0
// formatter(pkgName);
// }
}
if (packageName.indexOf("tsup") != -1) {
console.log(packageName.split("@"));
console.log("scopedPkgName", scopedPkgName);
console.log("pkgName", pkgName);
console.log("version", version);
}
// https://registry.npmmirror.com/@babel/code-frame/-/code-frame-7.23.5.tgz
// https://registry.npmmirror.com/ignore/-/ignore-5.3.0.tgz
// scopedPkgName = @babel/code-frame , ignore
// pkgName = code-frame , ignore
// version = 7.23.5 , 5.3.0
var resolved = "https://registry.npmmirror.com/"
.concat(scopedPkgName, "/-/")
.concat(pkgName, "-")
.concat(version, ".tgz");
var dev = lockObj.dev || false;
var integrity =
((_d = lockObj.resolution) === null || _d === void 0
? void 0
: _d.integrity) || "";
var baseDepObj = {
version: version,
resolved: resolved,
integrity: integrity,
dev: dev,
};
var requires = ((_b = {}), (_b[scopedPkgName] = version), _b);
// var dependencies = ((_c = {}), (_c[scopedPkgName] = baseDepObj), _c);
var pkgDepObj = __assign(__assign({}, baseDepObj), {
requires: requires,
// dependencies: dependencies,
});
npmLockObject.packages[
"node_modules/".concat(scopedPkgName, "@").concat(version)
] = pkgDepObj;
npmLockObject.dependencies[scopedPkgName] = pkgDepObj;
});
return npmLockObject;
}
修改完后在代码里执行:pnpm-lock-to-npm-lock pnpm-lock.yaml,就会生成package-lock.json文件。
缺点:有可能因为pnpm版本原因或其他因素导致pnpm-lock.yaml里依赖的属性格式变化了需重新修改createLock方法对应代码逻辑去生成下载地址。
2.根据package-lock.json文件下载所有依赖的tgz文件。
先在代码文件夹里新建nodes和tgz文件夹。
先在项目里安装shelljs依赖,npmtgz脚本需要这个工具, pnpm i shelljs --save-dev。
原理:使用npmtgz.js这个脚本遍历package-lock里的依赖批量下载依赖的tgz文件。
在代码目录里执行 node npmtgz.js等待下载。
脚本代码如下
// npmtgz.js
const shell = require('shelljs')
const fs = require('fs')
function download(fileNames = []) {
shell.cd('tgz')
let count = 0
fileNames.forEach((fileName) => {
const fileExec = shell.exec(`npm pack ${fileName}`, {
async: true,
silent: true,
})
fileExec.stdout
.on('data', () => {
++count
// shell.echo(`>>> ${fileName} 下载完成...`)
if (count === fileNames.length) {
shell.cd('..')
shell.exit(0)
}
})
.on('err', () => {
++count
shell.echo(`>>> ${fileName} 下载失败!!!...`)
if (count === fileNames.length) {
shell.cd('..')
shell.exit(0)
}
})
})
}
function downloadByPackageJsonLockFile(depLockJsonFile = {}) {
const nMap = new Map() // 需要下载的包
const NotMap = new Map() // 已经下载过的包
// 总的nodes文件夹,方便下次避免重复下载
const downloadedDir = './nodes'
const downloadedArr = fs.readdirSync(downloadedDir)
function getAllList(depJson) {
if (depJson) {
Object.keys(depJson).forEach((dep) => {
// 下载连接
const depWithVersion = depJson[dep].resolved
// const depWithVersion = `${dep}@${depJson[dep].version}`
// 包名@版本号
let tgzFormat = `${dep}.tgz`
// eg: @babel/code-frame-7.14.5.tgz -> babel-code-frame-7.14.5.tgz
// eg: "node_modules/@windicss/config@1.9.3"
if (dep.startsWith('node_modules/')) {
tgzFormat = tgzFormat.split('node_modules/')[1]
// eg: '@windicss/config@1.9.3'
if (tgzFormat.startsWith('@')) {
let arr = tgzFormat.split('@')
if (arr[1].indexOf('/') != -1) {
let arr2 = arr[1].split('/')
tgzFormat = arr2.join('-') + '-' + arr[2]
} else {
tgzFormat = arr[1] + '-' + arr[2]
}
} else {
tgzFormat = tgzFormat.split('@').join('-')
}
// if (dep.indexOf('plugin-proposal-private') != -1) {
// console.log('tgzFormat', tgzFormat)
// }
// console.log('tgzFormat', tgzFormat)
} else {
tgzFormat = dep.startsWith('@')
? tgzFormat.split('/').join('-').slice(1)
: tgzFormat
}
if (!nMap.has(depWithVersion) && !downloadedArr.includes(tgzFormat)) {
nMap.set(depWithVersion, true)
getAllList(depJson[dep].dependencies)
} else if (
downloadedArr.includes(tgzFormat) &&
!NotMap.has(tgzFormat)
) {
// 已经下载过的包
NotMap.set(tgzFormat, true)
}
})
}
}
// console.log('depLockJsonFile', depLockJsonFile.name)
getAllList(depLockJsonFile.packages)
// getAllList(depLockJsonFile.dependencies)
shell.echo(
`一共${
Array.from(NotMap.keys()).length
}个依赖包已在${downloadedDir}目录下存在,不需要重复下载:\n`
)
// shell.echo(
// `>>> 无需下载列表: \n - ${Array.from(NotMap.keys()).join('\n - ')}...\n`
// )
shell.echo(`一共${Array.from(nMap.keys()).length}个依赖包待下载\n`)
shell.echo(
`>>> 待下载列表: \n - ${Array.from(nMap.keys()).join('\n - ')}...`
)
download(Array.from(nMap.keys()))
}
const pkgLock = require('./package-lock')
downloadByPackageJsonLockFile(pkgLock)
3.把所有tgz文件上传到nexus的npm仓库
先登录当前私库账号 执行:npm adduser。
也可以在.npmrc配置_auth,加密规则浏览器自带的window.btoa(‘user:password’)。window.atob可解密。
以下脚本二选一。注意看日志信息!可能有些依赖会上传失败,失败的自己手动上传到nexus就好。
js脚本使用npmupload.js,要先安装nodejs才能执行。
// npmupload.js
let fs = require('fs')
let path = require('path')
const { exec } = require('child_process')
// 前端私库地址
const registry = 'http://localhost:8081/repository/npm-hosted-pnpm/'
const publishPosition = `npm publish --registry=${registry}`
// 待publish文件夹地址
const filesDir = './tgz/'
fs.readdir(filesDir, (errs, files) => {
files.forEach((file) => {
fs.stat(filesDir + file, (err, stats) => {
if (stats.isFile()) {
const fullFilePath = path.resolve(__dirname, filesDir + file)
console.log(fullFilePath + ' publish 开始')
exec(
publishPosition + ' ' + fullFilePath,
function (error, stdout, stderr) {
if (error) {
console.error(fullFilePath + ' publish 失败')
} else {
console.error(fullFilePath + ' publish 成功')
}
}
)
}
})
})
})
shell脚本使用 push.sh ,要先安装git才能执行。
#!/bin/bash
targetDir=./tgz
publishRestful=http://localhost:8081/service/rest/v1/components?repository=npm3
echo ">>> 文件所在目录:$targetDir <<<"
dir=$(ls -l $targetDir | awk '/.tgz$/ {print $NF}')
cd $targetDir
for file in $dir
do
echo ">>> $targetDir/$file 上传开始 \n"
ret=`curl -u admin:123456 -X POST "$publishRestful" -H "Accept: application/json" -H "Content-Type: multipart/form-data" -F "npm.asset=@$file;type=application/x-compressed"`
echo $ret
echo ">>> $targetDir/$file 上传完成 \n"
done
4.pnpm设置registry连接nexus下载依赖
在.npmrc配置auto-install-peers=false和registry=nexus私库的地址
最后一步执行pnpm i,下载完成后执行pnpm run dev运行代码。
5.更新或添加依赖
把tgz文件里的全部依赖放进nodes文件夹里。
外网环境里下载新依赖后,再根据pnpm-lock重新生成新的package-lock文件,用npmtgz.js脚本下载新的依赖,上传新依赖的tgz文件到nexus。