使用Node.js构建源代码

2023年8月1日目前,由于Raspbian是基于Bullseye版本,因此通过apt安装Node.js将会安装Node.js 12版本。
然而,由于zenn-cli不再支持Node.js 12及以下版本,在尝试从Bullseye的npm安装zenn-cli时会受到限制。尽管zenn-cli仍然可以安装,但会显示警告信息,标志为已弃用。

因此,我试着从树莓派上安装apt->npm->zenn-cli,并且没有遇到任何问题,感觉很好。
简单地说,我只需要将与node.js相关的软件包指向stable bookworm的存储库就行了,但即使这样,它也不是最新的LTS版本,既然不使用最新的LTS版本,那干脆就来尝试构建一下吧。

前提条件 tí

已安装了GitHub CLI。
已安装了tmux。
已安装了sysstat(必要用于统计。如果没有特定计划的话可以不安装)。

准备源代码

# gh repo clone用のディレクトリ作成
mkdir -p ~/src/nodejs
cd src/nodejs

从Github上下载源代码。

gh repo clone nodejs/node

cd node
# タグ一覧表示 
git tag

# 使いたいバージョンのタグにcheckout
git checkout v18.17.1

我们将在假设您位于项目根目录下的情况下进行说明。

建造方法

如果使用Node.js,则构建方法已在BUILDING.md文件中写明。
大多数项目都会写明本地构建方法,请按照该方法进行操作。
如果没有提供构建方法,您可以阅读源代码,查看构建时出现错误的lib文件,或者查看Makefile文件,自行调查。
除非是旧项目,否则很可能已经在某个地方写明了如何进行环境设置和操作方法。

Node.js 的说明非常详细,非常易懂。作为 README 和环境设置说明的写作示例,可以进行参考。

依赖关系

根据BUILDING.md文件的描述,Debian系统需要以下所列的软件包。

sudo apt install python3 g++ make python3-pip

关于Python,从Node.js v18(可能也包括v19但未经验证)开始,需要python3.9~3.6才能进行构建,请注意。而对于最新的Node.js v20,可以使用python3.11来进行构建。

配置

建立时的配置。
不用特别担心。

./configure

如果您不希望在root时发生/usr/local/bin和/usr/bin的干扰,可以按照以下方式更改安装目录。

# こうすると/usr/local/nodeのディレクトリを使ってその下にlibやbinを作ってくれる。
./configure --prefix=/usr/local/node

在使用时,可以将路径添加到PATH中,例如/usr/local/node/bin,或者直接执行/usr/local/node/bin/node。

建造

通过tmux启动。
这是因为通过ssh进行构建时,如果ssh会话中断,为了防止处理中断。
不一定需要tmux,但tmux是最不用考虑的选择,而且有很多爱好者,所以使用它可能是不错的选择。
在tmux中创建一个随意会话。

tmux

日志存放处

由于即使只是先构建和使用,也会花费相当多的时间,所以我认为最好至少保存日志。不然它们可能会流失。

只要不是特别指定的话,日志的位置可以随便选择,通常在构建普通用户时,我会创建一个类似于~/var/log/项目名称/make的目录,并将日志写入其中。在此先将这个位置作为日志存储位置。

mkdir -p ~/var/log/nodejs/node/make

帮助我

请查看Makefile文件中的help部分。

# make --helpでなくて、make helpであることに注意。
# make --helpだとmakeコマンドのhelpがでる。
make help

确认以下事项:使用make install时,默认的安装位置是/usr/local;使用make clean可以删除缓存;确保存在make uninstall(尽管有些项目可能无法通过make uninstall进行卸载)。

试试构建。

让我们尝试构建Makefile。
如果没有任何参数,就会默认执行make all来构建所有内容。

make -j是在进行构建时指定的作业数量。请根据核心数选择作业数量。

在这里的BUILDING.md中写着要以make -j4的指令进行构建,
但是对于像树莓派这样性能较差的电脑来说,可能更好的是使用-j2、-j3。
这是因为在性能较差的电脑上,后台处理相对较重,所以在构建过程中可能会导致桌面停止运行或崩溃的可能性。

我认为只有在实际环境中运行才能真正理解这些东西。

如果你关心核心数,可以查看lscpu或/proc/cpuinfo以调查核心数。

为了避免在构建过程中日志丢失,最好按照以下方式记录日志。
这样做可以避免在失败时进行繁琐的回溯操作。

# こうすると時間経過もログに残せる。
make -j4 2>&1 | awk '{print strftime("%Y-%m-%dT%H:%M:%S"), $0}' | tee ~/var/log/nodejs/node/make/"$(date +'%Y%m%dT%H%M%SZ')".log

如果想知道电脑的规格是否足够强大,可以在这个时候使用tmux打开一个新的会话,来检查负载情况。如果有可能需要多次构建,那最好进行测试。

mkdir -p ~/var/log/nodejs/node/iostat/
# これをやると1秒間に一回、cpuやディスクioの統計をとってくれる。
iostat -x 1 | awk '{print strftime("%Y-%m-%dT%H:%M:%S"), $0}' | tee ~/var/log/nodejs/node/iostat/"$(date +'%Y%m%dT%H%M%SZ')".log

一旦开始构建,就将tmux分离,关闭ssh,并且让其自行完成构建。

在建设时间中的小知识

尽管没接触过cpp的人可能会感到惊讶,但会有很多警告出现。
另外,由于会产生大量的日志,可能会令人惊讶,但这是常见的情况。
只要没有遇到错误而停止运行,那么一切都正常,请放心。

我用Raspberry Pi 4B进行构建后,使用iostat进行监测发现,用户级别的CPU使用率始终在90%左右,大约花费了3到4个小时。考虑到Raspberry Pi上的数据存储在MicroSD卡上,所以磁盘I/O也可能成为瓶颈,但实际上几乎没有观察到iowait现象,统计数据显示CPU的影响更大。

如果想要稍微减轻负担,尤其是在使用raspberrypi构建的情况下,可能最好关闭GUI。
在我第一次在raspberry pi4B上构建时,它崩溃了,所以我将其设置为cui,并重新进行了构建。

这些数值仅仅是根据我的环境来说的,我认为速度还会受到高温、低温、MicroSD卡的剩余容量和类型,以及电源是否充足等方面的影响。供参考。

不过,如果使用最新的电脑的话,可能只需要30分钟到1小时就能完成,所以使用新电脑的人可能不太需要担心。

试用新鲜出炉的产品。

如果可以成功构建,我会试着使用它。
我认为执行下面的命令会弹出对话框式界面。

./out/Realease/node

由于这个版本是发布版本并且在bookworm中也采用了,所以我认为应该没问题,但在进行make install之前,先进行简单的操作确认一下吧。

安装

我会将其安装在指定的安装位置。
在执行时会考虑依赖关系,并将bin、include等文件放置在适当的位置。
整个过程大约需要2~5分钟。

在Linux中,通常情况下,通过Makefile构建而不是使用apt、dnf等软件包管理工具安装的自定义软件应该放置在/usr/local/目录下。
根据前面的说明,执行下面的命令将把各个文件放置在/usr/local/目录下。

sudo make install

我希望将构建的多个版本分开使用。

如果想要在预览或其他情况下自己构建的版本之间进行切换,就需要实现一种类似虚拟环境或者类似于env的机制。

我会在构建时为node创建一个深层次的根目录,暂时进行调整。

在版本上写入构建版本,但是请注意,在更新或删除时,如果没有前缀,可能无法正确删除nodejs和npm库的文件。

# 17.2-previewとか最新のmainならunstableとか適当に名前をつける。
version=18.17.1
./configure --prefix=/usr/local/node/version/${version}

在执行sudo make install时,它会自动安装。

sudo make install

如果这样做,将node和npm的符号链接放在/usr/local/node/bin中会很方便。

sudo ln -s /usr/local/node/version/${version}/bin/node  /usr/local/node/bin/node
sudo ln -s /usr/local/node/version/${version}/bin/npm   /usr/local/node/bin/npm
sudo ln -s /usr/local/node/version/${version}/bin/npx   /usr/local/node/bin/npx

为什么不把可执行文件放在/usr/local/bin目录下呢?因为根目录默认情况下优先选择/usr/local/bin,而不是/usr/bin。如果在/usr/local/bin和/usr/bin中都放置相同的文件,可能会对系统产生影响。

当需要使用 /usr/local/node/bin 时,可将其添加到路径中。

如果要使用多个版本,请始终使用nodejs和npm
请注意版本符号链接是否正确。
如果觉得麻烦,可以自己制作一个类似的env并保存为脚本。
在这里,我们将假设它被命名为nodelocals。

#!/usr/bin/env bash
# 
# nodejs /usr/local/node/version/\$version version management

#######################################
# nodejs /usr/local/node/version/\$version version management
# Globals:
#   None
# Arguments:
#   All.
# Outputs:
#   format 
# Returns:
#   0 if command success, non-zero on error.
# Example:
#   nodelocals list
#   # out put installed version from /usr/local/node/version/\$version.
#   nodelocals set \$version
#   # set node, npm, npx to /usr/local/node/bin
#######################################
function nodelocals() {

  local i
  local new_array=( $@ )
  local version
  # nodeがない場合は空文字
  local selected_version=$(readlink /usr/local/node/bin/node | awk  -F/ '{print $6}')
  for ((i=0;i<$#;i++)); do
    # if find list flags from args, show installed nodejs destination /usr/local/node/bin/node.
    if [ "${new_array[$i]}" = "list" ]; then
      # 引数がlist以外にあるとおかしい。
      if [ -n "$2" ]; then
        echo 'list is no argument.' >&2
        return 1
      fi
      # echo 使っているものに*がつくようにする。
      local selected
      for version in $(ls -1 /usr/local/node/version); do
	      selected=" "
        if [ "${version}" = "${selected_version}" ]; then
          selected='*'
        fi
        # アスタリスクをそのままecho に通すと展開されるので""をつける。
        echo "$selected" $version
      done
      return 0
    fi
    # localが選ばれたら、
    if [ "${new_array[$i]}" = "set" ]; then
      if [ ${UID} -ne "0" ] && [ ${EUID} -ne "0" ]; then
        echo 'this function use superuser only' >&2
        return 1
      fi
      # version取得
      shift
      version=$1
      local check_version
      local exist=1
      # versionの存在確認
      for check_version in $(ls -1 /usr/local/node/version); do
        if [ "${version}" = "${check_version}" ]; then
          exist=0
        fi
      done
      if [ "$exist" = "1" ]; then
        echo 'select installed version!' >&2
        return 1
      fi
      # symlinkで紐付け
      ln -snf /usr/local/node/version/${version}/bin/node  /usr/local/node/bin/node
      ln -snf /usr/local/node/version/${version}/bin/npm   /usr/local/node/bin/npm
      ln -snf /usr/local/node/version/${version}/bin/npx   /usr/local/node/bin/npx
      return 0
    fi
  done
}

function usage() {
    cat 1>&2 <<EOF
nodelocals
nodejs /usr/local/node/version/\${version} allignment version management tools

USAGE:
    nodelocals [FLAGS] [OPTIONS]

FLAGS:
    list                    Show /usr/local/node/version/\$version installed nodejs version engines.
    set                     Set node, npm, npx to /usr/local/node/bin
    -h, --help              Prints help information

OPTIONS:
    --debug                 Set bash debug Option
EOF
}

function main {

  local i
  local new_array=( $@ )
  for ((i=0;i<$#;i++)); do
    if [ "${new_array[$i]}" = "--help" ] || [ "${new_array[$i]}" = "-h" ]; then
      usage
      return
    fi
    # if find --debug flag from args, start debug mode.
    if [ "${new_array[$i]}" = "--debug" ]; then
      set -x
      trap "
        set +x
        trap - RETURN
      " RETURN
      unset new_array[$i]
    fi
  done
  # reindex assign.
  new_array=${new_array[@]}

  nodelocals $new_array
}

main $@

我会给予这个权限。

sudo chmod 755 /usr/local/bin/nodelocals

我会为每个环境创建一个指向bin目录的符号链接文件夹。

sudo mkdir -p  /usr/local/node/bin

使用方法

# /usr/local/nodeにインストールしたnodejs一覧表示
nodelocals list

# /usr/local/nodeに存在する指定したバージョンのnode, npm, npxを/usr/local/binにセットする。
sudo nodelocals set 18.9.1

如果添加了安装处理或其他内容,那将成为某种env。
我认为实施本身相当简单。

然而,如果做得太過火,就會變成對nodeenv、nvm或n這些車輪的重新發明,所以要適可而止。
問題的焦點是想要自己完全管理還是不想要呢?
比如想在像/usr/local這樣的系統中放置多個版本。

尝试使用已安装在/usr/local/ 的内容。

如果/usr/local/bin的路径被设置 properly, 应该可以被正常地执行。

# 存在確認
command -v npm
command -v node

node -v
## 対話形式で実行してみる。
node

## zenn-cliをインストールしてみる。
sudo npm install -g zenn-cli

如果按照上面所写的方法更改了安装路径,那么应该能够根据节点版本的不同,npm的安装路径也会发生相应的更改。让我们来确认一下。

# bin
ls -1 /usr/local/node/version/${version}/bin
# lib
ls -1 /usr/local/node/version/${version}/lib

卸载

有些情况下,Makefile可能没有提供卸载方法。在这种情况下,您需要手动删除。但是,nodejs已经编写了卸载指令,因此,只需在Makefile所在位置执行以下步骤即可完成卸载。

sudo make uninstall

如果按照上述方法根据不同版本更改安装位置,那么与该nodejs相关的库和二进制文件将保存在/usr/local/node/${version}/路径下。因此,可以通过在每个版本的环境中使用rm -rf命令来删除它们。

# nodejs18.17.1の環境を削除する時
version=18.17.1
rm -rf /usr/local/node/version/${version}

虽然可以在之前提到的nodelocals上添加类似卸载的功能,但考虑到自己编写的管理成本以及方便性,可能并不是很值得。如果其他人使用的话可能会有需要。

想要更新

卸载后,先进行清理,然后使用适当的git版本,进行配置、编译、安装。

在执行git check-out之前,请先进行清理操作。如果使用make install进行覆盖安装,系统可能会保留之前版本的依赖库。请注意。

## 残っているプロジェクトのMakefileを使ってアンインストール
sudo make uninstall

# キャッシュクリーン
make clean

# 使いたいバージョンにチェックアウト
git checkout v{適切なバージョン}

./configure

make 

sudo make install

总结

如果按照步骤做,我认为没有特别困难的地方。

你可能已经注意到了,如果在Makefile中不将版本区分开并安装到系统中,那么在卸载和更新时会出现问题。所以请确保保留项目。

如果按照上述步骤进行操作,您可以了解在构建过程中所承受的负担有多大。所以,如果有人想要为Node.js做出贡献,或者想要成为JS王之类的人,但是负担太重,花费的时间太长,就会觉得无法忍受了。最好还是乖乖购买一台专门用于Node.js开发环境的计算机吧。

bannerAds