跳到主要内容

4 篇博文 含有标签「npm」

查看所有标签

在离线服务器上安装 Node 模块

· 阅读需 7 分钟
1adybug
子虚伊人

最近有需求,需要在一台离线服务器上安装 node_modules,尝试了以下办法:

  1. 直接在外网 Windows 11 上将依赖安装好,将整个项目进行压缩,然后传输到离线服务器上,再进行解压。结果失败,因为某些包会执行 postinstall 脚本,编译一些系统相关的代码,导致失败。
  2. 先试用 npm install 安装依赖,然后使用 package-lock.json 文件将整个项目的依赖树全部下载成 tgz 文件,然后在内网执行 npm install --no-registry --cache ./npm-cache --prefer-offline 安装依赖。结果失败,原因未知。
  3. WSL 中创建与离线服务器相同的系统环境,然后执行 npm install 安装依赖。打包再解压,依旧失败,与方法 1 相同,依旧提示缺少某些依赖。

最后一个办法是使用 verdaccio 搭建一个私有仓库。

安装 verdaccio

这里使用 docker 安装 verdaccio,因为部署起来比较方便。

docker run -d --name verdaccio -p 4873:4873 verdaccio/verdaccio

或者进行目录挂载

docker run -d --name verdaccio -p 4873:4873 -v /path/to/verdaccio/conf:/verdaccio/conf -v /path/to/verdaccio/storage:/verdaccio/storage verdaccio/verdaccio

这里有两种选择,一种是将仓库数据挂载到 docker 容器中,一种是直接将仓库数据挂载到本地。

第一种方法,需要每次都将容器导出为新的镜像,然后推送到离线服务器上。

第二种方法,直接将仓库数据挂载到本地,然后每次启动容器时,将本地仓库数据挂载到容器中,需要注意的是,必须先使用第一种方法,先从容器中导出默认的配置文件,放在 /path/to/verdaccio/conf 目录下,或则复制下方配置文件 config.yaml/path/to/verdaccio/conf 目录下,否则容器会报错。

#
# This is the default configuration file. It allows all users to do anything,
# please read carefully the documentation and best practices to
# improve security.
#
# Do not configure host and port under `listen` in this file
# as it will be ignored when using docker.
# see https://verdaccio.org/docs/en/docker#docker-and-custom-port-configuration
#
# Look here for more config file examples:
# https://github.com/verdaccio/verdaccio/tree/6.x/conf
#
# Read about the best practices
# https://verdaccio.org/docs/best

# path to a directory with all packages
storage: /verdaccio/storage/data
# path to a directory with plugins to include
plugins: /verdaccio/plugins

# https://verdaccio.org/docs/webui
web:
title: Verdaccio
# comment out to disable gravatar support
# gravatar: false
# by default packages are ordercer ascendant (asc|desc)
# sort_packages: asc
# convert your UI to the dark side
# darkMode: true
# html_cache: true
# by default all features are displayed
# login: true
# showInfo: true
# showSettings: true
# In combination with darkMode you can force specific theme
# showThemeSwitch: true
# showFooter: true
# showSearch: true
# showRaw: true
# showDownloadTarball: true
# HTML tags injected after manifest <scripts/>
# scriptsBodyAfter:
# - '<script type="text/javascript" src="https://my.company.com/customJS.min.js"></script>'
# HTML tags injected before ends </head>
# metaScripts:
# - '<script type="text/javascript" src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script>'
# - '<script type="text/javascript" src="https://browser.sentry-cdn.com/5.15.5/bundle.min.js"></script>'
# - '<meta name="robots" content="noindex" />'
# HTML tags injected first child at <body/>
# bodyBefore:
# - '<div id="myId">html before webpack scripts</div>'
# Public path for template manifest scripts (only manifest)
# publicPath: http://somedomain.org/

# https://verdaccio.org/docs/configuration#authentication
auth:
htpasswd:
file: /verdaccio/storage/htpasswd
# Maximum amount of users allowed to register, defaults to "+infinity".
# You can set this to -1 to disable registration.
# max_users: 1000
# Hash algorithm, possible options are: "bcrypt", "md5", "sha1", "crypt".
# algorithm: bcrypt # by default is crypt, but is recommended use bcrypt for new installations
# Rounds number for "bcrypt", will be ignored for other algorithms.
# rounds: 10

# https://verdaccio.org/docs/configuration#uplinks
# a list of other known repositories we can talk to
uplinks:
npmjs:
url: https://registry.npmmirror.com/

# Learn how to protect your packages
# https://verdaccio.org/docs/protect-your-dependencies/
# https://verdaccio.org/docs/configuration#packages
packages:
"@*/*":
# scoped packages
access: $all
publish: $authenticated
unpublish: $authenticated
proxy: npmjs

"**":
# allow all users (including non-authenticated users) to read and
# publish all packages
#
# you can specify usernames/groupnames (depending on your auth plugin)
# and three keywords: "$all", "$anonymous", "$authenticated"
access: $all

# allow all known users to publish/publish packages
# (anyone can register by default, remember?)
publish: $authenticated
unpublish: $authenticated

# if package is not available locally, proxy requests to 'npmjs' registry
proxy: npmjs

# To improve your security configuration and avoid dependency confusion
# consider removing the proxy property for private packages
# https://verdaccio.org/docs/best#remove-proxy-to-increase-security-at-private-packages

# https://verdaccio.org/docs/configuration#server
# You can specify HTTP/1.1 server keep alive timeout in seconds for incoming connections.
# A value of 0 makes the http server behave similarly to Node.js versions prior to 8.0.0, which did not have a keep-alive timeout.
# WORKAROUND: Through given configuration you can workaround following issue https://github.com/verdaccio/verdaccio/issues/301. Set to 0 in case 60 is not enough.
server:
keepAliveTimeout: 60
# Allow `req.ip` to resolve properly when Verdaccio is behind a proxy or load-balancer
# See: https://expressjs.com/en/guide/behind-proxies.html
# trustProxy: '127.0.0.1'

# https://verdaccio.org/docs/configuration#offline-publish
# publish:
# allow_offline: false

# https://verdaccio.org/docs/configuration#url-prefix
# url_prefix: /verdaccio/
# VERDACCIO_PUBLIC_URL='https://somedomain.org';
# url_prefix: '/my_prefix'
# // url -> https://somedomain.org/my_prefix/
# VERDACCIO_PUBLIC_URL='https://somedomain.org';
# url_prefix: '/'
# // url -> https://somedomain.org/
# VERDACCIO_PUBLIC_URL='https://somedomain.org/first_prefix';
# url_prefix: '/second_prefix'
# // url -> https://somedomain.org/second_prefix/'

# https://verdaccio.org/docs/configuration#security
# security:
# api:
# legacy: true
# # recomended set to true for older installations
# migrateToSecureLegacySignature: true
# jwt:
# sign:
# expiresIn: 29d
# verify:
# someProp: [value]
# web:
# sign:
# expiresIn: 1h # 1 hour by default
# verify:
# someProp: [value]

# https://verdaccio.org/docs/configuration#user-rate-limit
# userRateLimit:
# windowMs: 50000
# max: 1000

# https://verdaccio.org/docs/configuration#max-body-size
# max_body_size: 10mb

# https://verdaccio.org/docs/configuration#listen-port
# listen:
# - localhost:4873 # default value
# - http://localhost:4873 # same thing
# - 0.0.0.0:4873 # listen on all addresses (INADDR_ANY)
# - https://example.org:4873 # if you want to use https
# - "[::1]:4873" # ipv6
# - unix:/tmp/verdaccio.sock # unix socket

# The HTTPS configuration is useful if you do not consider use a HTTP Proxy
# https://verdaccio.org/docs/configuration#https
# https:
# key: ./path/verdaccio-key.pem
# cert: ./path/verdaccio-cert.pem
# ca: ./path/verdaccio-csr.pem

# https://verdaccio.org/docs/configuration#proxy
# http_proxy: http://something.local/
# https_proxy: https://something.local/

# https://verdaccio.org/docs/configuration#notifications
# notify:
# method: POST
# headers: [{ "Content-Type": "application/json" }]
# endpoint: https://usagge.hipchat.com/v2/room/3729485/notification?auth_token=mySecretToken
# content: '{"color":"green","message":"New package published: * {{ name }}*","notify":true,"message_format":"text"}'

middlewares:
audit:
enabled: true

# https://verdaccio.org/docs/logger
# log settings
log: { type: stdout, format: pretty, level: http }
#experiments:
# # support for npm token command
# token: false
# # enable tarball URL redirect for hosting tarball with a different server, the tarball_url_redirect can be a template string
# tarball_url_redirect: 'https://mycdn.com/verdaccio/${packageName}/${filename}'
# # the tarball_url_redirect can be a function, takes packageName and filename and returns the url, when working with a js configuration file
# tarball_url_redirect(packageName, filename) {
# const signedUrl = // generate a signed url
# return signedUrl;
# }

# translate your registry, api i18n not available yet
# i18n:
# list of the available translations https://github.com/verdaccio/verdaccio/blob/master/packages/plugins/ui-theme/src/i18n/ABOUT_TRANSLATIONS.md
# web: en-US

按需选择。

配置 verdaccio

主要是将 npmjs 的地址改为国内的镜像地址,或者直接将 npmjs 的地址改为离线服务器地址。

uplinks:
npmjs:
url: https://registry.npmmirror.com/

重新安装依赖

  1. 删除 node_modules 文件夹

    npx rimraf node_modules
  2. 删除 package-lock.json 文件

  3. 删除 npm 缓存,这一步非常重要,否则某些依赖可能会无法被缓存到 verdaccio 中

    npm cache clean --force
  4. 安装依赖

    npm install --registry http://localhost:4873

离线环境安装

  1. 部署 docker 容器

  2. 安装依赖

    npm install --registry http://localhost:4873

非 npm 包安装

有一些依赖并非通过 npm 发布,而是自己的渠道,这时可以执行以下方案:

  1. 下载并解压 tgz 文件

  2. 执行 npm addUser --registry http://localhost:4873 登录

  3. 在项目根目录执行 npm publish --registry http://localhost:4873 发布

  4. 在内网环境中的 package.json 中添加以下配置:

    "overrides": {
    "your-package": "npm:your-package@version"
    }
  5. 安装依赖

使用 npm 安装最新的包

· 阅读需 1 分钟
1adybug
子虚伊人

有时候我们在更新了自己的 npm 包之后,需要立即在项目中进行更新,但是 registry 上的包并没有及时更新。这时候我们可以使用 npm i 命令的 @latest 标记配合 --registry https://registry.npmjs.com 来安装最新的包。

npm i soda-next@latest --registry https://registry.npmjs.com

使用 PowerShell,可以在 $PROFILE 中配置:

function inpm {
$package = $args | ForEach-Object { "$_@latest" }
npm i $package --registry=https://registry.npmjs.org
}

发布后同步到 npm 镜像

· 阅读需 2 分钟
1adybug
子虚伊人

在发布 npm 包后,可以立即在 npm 上看到最新的版本,然而 npmmirror 等镜像站却有一定的延迟。如果想要立即同步到镜像站,可以使用以下方法:

  1. 在项目根目录创建 scripts 文件夹,创建 sync.mjs 文件

    // @ts-check

    import { readFile } from "fs/promises"

    /**
    * 将浏览器中直接复制的 headers 转换为对象
    * @param {string} str 复制的 headers
    * @returns {Headers} headers 对象
    */
    function getHeaders(str) {
    const reg = /^(.+?):$\n^(.+?)$/gm
    const reg2 = new RegExp(reg.source, "m")
    const headers = new Headers()
    const match = str.match(reg)
    if (!match) throw new Error("headers 格式错误")

    Array.from(match).forEach(item => {
    const match2 = item.match(reg2)
    headers.set(match2[1], match2[2])
    })

    return headers
    }

    const headers = getHeaders(`accept:
    */*
    accept-encoding:
    gzip, deflate, br, zstd
    accept-language:
    zh-CN,zh;q=0.9,en;q=0.8
    content-length:
    0
    dnt:
    1
    origin:
    https://npmmirror.com
    priority:
    u=1, i
    referer:
    https://npmmirror.com/
    sec-ch-ua:
    "Google Chrome";v="131", "Chromium";v="131", "Not_A Brand";v="24"
    sec-ch-ua-mobile:
    ?0
    sec-ch-ua-platform:
    "Windows"
    sec-fetch-dest:
    empty
    sec-fetch-mode:
    cors
    sec-fetch-site:
    same-site
    user-agent:
    Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36`)

    /**
    * 同步包
    * @param {string} packageName 包名
    */
    function syncPackage(packageName) {
    return fetch(`https://registry-direct.npmmirror.com/-/package/${packageName}/syncs`, {
    headers,
    referrer: "https://npmmirror.com/",
    referrerPolicy: "strict-origin-when-cross-origin",
    method: "PUT",
    mode: "cors",
    credentials: "omit",
    })
    }

    async function main() {
    const packageJson = JSON.parse(await readFile("package.json", "utf-8"))
    await syncPackage(packageJson.name)
    }

    main()
  2. package.json 中添加 sync 脚本

    {
    "scripts": {
    "postpublish": "node scripts/sync.mjs"
    }
    }

检查依赖中的包

· 阅读需 2 分钟
1adybug
子虚伊人

有时候,项目中可能会存在某个隐藏的依赖,又或者存在同一个依赖的多个版本,这时候我们可以通过以下命令来检查依赖来源:

# 推荐:npm
npm ls @types/react
# yarn
yarn why @types/react

当项目中存在多个版本的 @types/react 时,可能或报错:

不能用作 JSX 组件, 不是有效的 JSX 元素

这时我们可以在 package.json 中配置 resolutions 统一 @types/react 版本:

{
"resolutions": {
"@types/react": "^18.2.79"
}
}

以下是实际遇到的案例,在启动某项目时,总是报以下错误:

错误

去 github 一搜,发现是项目中存在多个版本 string-width 依赖(实际上只会安装最新的依赖)的问题,使用 npm ls string-width 查看:

之前

将依赖项 string-width 版本改为 ^4

{
"resolutions": {
"string-width": "^4"
}
}

再使用 npm ls string-width 查看:

之前

问题解决了。

注意
  1. 尽量使用多个版本中最低 major 版本的依赖,新版本可能是移除了某些特性
  2. 解决这一个依赖的版本问题后,再次启动项目,可能还会报错,不要慌张,仔细看一下报错信息,有可能是另一个依赖的版本问题