首页 前端知识 vue-pnpm项目迁移至内网环境-超详细

vue-pnpm项目迁移至内网环境-超详细

2024-05-13 10:05:40 前端知识 前端哥 948 371 我要收藏

主要是把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。

转载请注明出处或者链接地址:https://www.qianduange.cn//article/8517.html
标签
评论
会员中心 联系我 留言建议 回顶部
复制成功!