成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

如何在 Npm 上發(fā)布二進制文件?

開發(fā) 前端
作為一個cli工具,我們的f_cli需要發(fā)配給團隊伙伴使用。此時就會出現(xiàn)一個問題,團隊伙伴的開發(fā)環(huán)境(處理器架構(gòu)/操作系統(tǒng))可能和我們本機不一樣,所以我們需要將Rust編譯成適配不同的處理器架構(gòu)和操作系統(tǒng)。

??????號外,號外。我們的f_cli現(xiàn)在有了npm版本了。有兩種主流的方式來訪問。

  1. 全局安裝

npm i -g f_cli_f

f_cli_f create 你的項目名稱

  1. npx 操作
  2. npx f_cli_f create 你的項目名稱

隨意選中任意一個方式,不出意外的話,就在指定的文件路徑下,生成了一個功能完備的前端項目。

前言

我們主要的精力放在如何配置一個「功能全備」的前端項目。

然后,有些同學說,既然cli都有了,但是下載二進制文件很麻煩。最好是將f_cli發(fā)布到npm上。畢竟,在前端開發(fā)中,npm大家都熟悉。

所以,今天我們就來講講「如何將二進制文件發(fā)布到npm」。

好了,天不早了,干點正事哇。

我們能所學到的知識點

  1. Rust項目交叉編譯
  2. 構(gòu)建&發(fā)布目標npm項目
  3. 構(gòu)建&發(fā)布主包
  4. 本地應(yīng)用

1. Rust項目交叉編譯

要將源代碼編譯到與本地平臺不同的平臺上,需要指定一個目標(target)。這將告訴編譯器應(yīng)該為哪個平臺編譯代碼。

確定target

作為一個cli工具,我們的f_cli需要發(fā)配給團隊伙伴使用。此時就會出現(xiàn)一個問題,團隊伙伴的開發(fā)環(huán)境(處理器架構(gòu)/操作系統(tǒng))可能和我們本機不一樣,所以我們需要將Rust編譯成適配不同的處理器架構(gòu)和操作系統(tǒng)。

以下是我們工作中比較常見的開發(fā)環(huán)境。

  • Darwin(arm)
  • Darwin(arm64)
  • Darwin(x64)
  • Linux (arm)
  • Windows (i686)
  • Windows (x64)

針對f_cli我們只兼容比較場景的開發(fā)環(huán)境。(后期有需要會兼容更多版本)

  • Darwin(arm64) - MacOS的M1版本
  • Darwin(x64) - MacOS的Intel版本
  • Windows (x64) - Windows

安裝指定target

我們要想將Rust項目編譯成指定的目標二進制,我們可以在cargo build時,使用--target xxx參數(shù)來指定目標環(huán)境。

還記得rustup嗎?我們在Rust環(huán)境配置和入門指南中有過介紹。

rustup的命令行工具來完成Rust的下載和安裝,這個工具被用來管理不同的Rust發(fā)行版本及其附帶工具鏈。

其實rustup除了安裝和更新Rust,它還可以查看rust在交叉編譯[1]時,能夠轉(zhuǎn)換的目標環(huán)境。

我們可以通過rustup target list來查看這些信息。

圖片圖片

上圖是我本機已經(jīng)安裝的target。(我多加了一個參數(shù)--installed)

  • aarch64-apple-darwin -支持Mac Arm
  • x86_64-apple-darwin - 支持Mac Intel(也是我本機環(huán)境)
  • x86_64-pc-windows-gnu - 支持Windows環(huán)境

其中wasm32-unknown-unknown是我們處理Rust轉(zhuǎn)WebAssembly時,才用到。關(guān)于這點,可以參考我們之前的文章Rust 編譯為WebAssembly 在前端項目中使用。

既然,目標環(huán)境已經(jīng)確定,那我們就需要將目標環(huán)境加入到Rust環(huán)境中。

rustup target add xxxx

通過上述命令,我們就將xxxx的環(huán)境加入到Rust中。除了像上面使用rustup target list --installed來查看已經(jīng)安裝的目標環(huán)境。

我們也可以使用rustup show來查看本機的工具環(huán)境。

圖片圖片

執(zhí)行編譯

其實這步也沒啥可說的。要想Rust編譯成目標環(huán)境我們僅需在cargo build時,新增target參數(shù)即可。

cargo build --release ----target = xxxx

在執(zhí)行完build后,會在Rust項目中target目錄下生成對應(yīng)的編譯結(jié)果。

圖片圖片

由于我本機屬于x86_64-apple-darwin,所以在build時可以不加target參數(shù)。

然后我們可以在目標目錄中的release中找到f_cli二進制文件。

圖片圖片

針對Windows環(huán)境的特殊處理

在MacOS中將Rust編譯為可以在Windows環(huán)境下執(zhí)行的二進制時,需要做額外的處理。

圖片圖片

更多詳情可以參考如何在 Mac 上為 Windows 編譯 Rust 程序[2]

2. 構(gòu)建&發(fā)布目標npm項目

我們的目標是- 將build后的二進制文件放置到npm包中,然后通過node進行下載安裝。

如果將所有平臺的二進制放到一個npm是極其耗費流量的。所以,我們采用的是「按需下載」的方式。

所以,我們就把上一節(jié)中交叉編譯的三個二進制文件「分別發(fā)布」成一個npm包。

  • f_cli_darwin_arm64
  • f_cli_darwin_x64
  • f_cli_windows_x64

對于快速構(gòu)建一個npm目錄我們可以使用npm init然后一路回車。但是,我們不這樣做,我們這里采用手動構(gòu)建package.json。然后配置一些參數(shù)即可。關(guān)于package.json中各個字段的含義,可以參考package.json的字段信息[3]

子包的目錄結(jié)構(gòu)

由于我們子包的作用就是存儲二進制文件,所以我們采用最簡單的目錄結(jié)構(gòu)

由于子包的處理邏輯很類似,我們下文中除了要特殊說明,都是按照一個子包的處理方式來講解

"f_cli_darwin_arm64"/"f_cli_darwin_x64"
 ├── package.json
 └── bin/
     └── f_cli

"f_cli_windows_x64"
 ├── package.json
 └── bin/
     └── f_cli.exe

bin文件夾中就是存放我們二進制源文件的,這里沒啥可說的。我們來簡單聊聊package.json

package.json

下面的package.json的內(nèi)容是f_cli_darwin_arm64的。其他兩個子包的信息也是大差不差的。

{
  "name": "f_cli_darwin_arm64",
  "version": "1.0.0",
  "description": "f_cli適配MACOS_ARM64架構(gòu)",
  "keywords": [
    "f_cli",
    "MACOS_ARM64"
  ],
  "author": "",
  "license": "ISC",
  "os": ["darwin"],
  "cpu": ["arm64"]
}

其中有幾個屬性我們需要額外說明一下:

  1. name該字段是我們發(fā)布npm包時,最主要的字段,你可以將起認為是數(shù)據(jù)庫中的主鍵,我們平時通過npm install xxx安裝包時,xxx就是此處的name的值

在發(fā)布包之前,我們可以為其指定具有特殊含義的名稱,同時該名稱需要在npm倉庫中唯一,不然在npm publish時就會發(fā)生錯誤

同時該名稱的格式也有要求,它需要符合^(?:(?:@(?:[a-z0-9-*~][a-z0-9-*._~]*)?/[a-z0-9-._~])|[a-z0-9-~])[a-z0-9-._~]*$正則規(guī)則

  1. os:指定模塊將在哪些操作系統(tǒng)上運行
  • 該值由node中的process.platform[4]決定,用于獲取操作系統(tǒng)平臺信息。
  • 值為aix, android, darwin, freebsd, linux, openbsd, sunprocess, win32
  1. cpu:指定代碼只能在某些 CPU 架構(gòu)上運行
  • 該值由node中的process.arch[5]決定,用于獲取操作系統(tǒng)平臺信息。
  • 值為x32, x64, arm, arm64, s390, s390x, mipsel, ia32, mips, ppc, ppc64.

我們后期會有關(guān)于package.json各個字段的介紹文章

發(fā)布子包到npm

其實這步特別簡單就是兩個命令

  • npm login
  • npm publish

對于如何發(fā)布一個npm包,這里我們就不再贅述。后期如果有需求可以單寫一篇。

通過上述的操作,我們就把三個二進制文件發(fā)布到npm上了。

圖片圖片

上面還有一個f_cli_f,別著急,我們馬上會講到。

3. 構(gòu)建&發(fā)布主包

上面我們通過各自上傳子包到npm,實現(xiàn)了資源的分離處理。下面我們就需要通過一些方式讓主包在被安裝時,能夠自動識別出工作平臺所需要目標并且執(zhí)行對應(yīng)的下載和安裝任務(wù)。

簡而言之,我們需要在主包被安裝時,實現(xiàn)按需下載

npm 按需下載原理

在package.json中有兩種方式可以下載特定于平臺的二進制文件,而無需下載所有二進制文件。

optionalDependencies

所有常用的 JavaScript 包管理器都支持 package.json 中的 optionalDependencies[6] 字段。包管理器通常會安裝 optionalDependencies 中列出的所有軟件包,但他們可能會根據(jù)某些條件選擇不安裝。

其中一個標準就是依賴項 package.json 文件中的 os 和 cpu 字段。(我們在處理子包時就已經(jīng)把這些值賦值了)

「只有當這些字段的值與當前系統(tǒng)的操作系統(tǒng)和架構(gòu)相匹配時,才會安裝依賴包」。這意味著我們可以發(fā)布單獨的軟件包,每個軟件包只包含一個特定于平臺的二進制文件,但其中的os和cpu字段指明了這些軟件包適用的體系結(jié)構(gòu),軟件包管理器將自動安裝正確的軟件包。

postinstall 腳本

如果在 package.json 中包含一個名為 postinstall 的腳本,則該腳本將在包安裝后「立即執(zhí)行」,即使它是作為安裝包安裝的一種依賴。(在前端項目里都有啥?,我們講過prepare,其實他們的作用是類似的)

我們可以使用 postinstall 腳本下載當前平臺的二進制文件并將其存儲在系統(tǒng)上的某個位置。其實我們可以把這個包的位置存放到任何你信得過的地方,此處我們?yōu)榱朔奖銓⒍M制文件都放置到了npm倉庫了。

最優(yōu)解

這兩種方法都有缺點,可能不適用于所有設(shè)置。

  • 如果禁用optionalDependencies可能會遇到問題(例如,通過yarn的--ignore-optional標志)。
  • postinstall 腳本也可以被禁用,并且可能會出現(xiàn)更多問題,因為通常建議禁用它們,因為它們?nèi)菀资艿焦簟?/li>

為了最大限度地提高成功的可能性,我們將兩種方式都融合進主包中。

目錄結(jié)構(gòu)

其實主包的目錄結(jié)構(gòu)也很簡單。和子包類似,有package.json/bin/二進制源文件

f_cli
 ├── install.js
 ├── package.json
 └── bin/
     └── f_cli

那么下面我們就依次解釋上面文件的含義。

package.json

{
  "name": "f_cli_f",
  "version": "1.0.3",
  "description": "針對f_cli的npm 包",
  "scripts": {
    "postinstall": "node ./install.js"
  },
  "bin": {
    "f_cli_f": "bin/cli"
  },
  "optionalDependencies": {
    "f_cli_darwin": "1.0.0",
    "f_cli_linux": "1.0.0",
    "f_cli_win32": "1.0.0"
  }
}

上面出現(xiàn)的scripts.postinstall和optionalDependencies我們在本節(jié)剛開始就解釋了。這里就不再啰嗦。

在這里我們來講講bin字段。

bin

bin 字段允許將包中的特定文件鏈接到全局的可執(zhí)行路徑,使其成為全局命令,方便用戶在命令行中直接調(diào)用。

bin 是 package.json 文件中的一個字段,用于定義「將包安裝為全局命令時的可執(zhí)行文件」。

bin 字段是一個對象,其中鍵是要創(chuàng)建的全局命令的名稱,值是要執(zhí)行的本地文件的路徑。

當用戶全局安裝該包時,bin 字段允許將指定的本地文件鏈接到全局的可執(zhí)行路徑,使用戶可以在命令行中直接運行該文件。

像上文中bin 字段為 { "f_cli_f": "bin/cli" },那么在全局安裝該包后,用戶可以直接在命令行中運行 f_cli_f,實際上會執(zhí)行 bin/cli 文件。

# 方式1: 全局按照
$ npm i -g f_cli_f
$ f_cli_f create xxx

# 方式2:包管理器
$ npx f_cli_f

install.js

// 引入必要的Node.js模塊
const fs = require('fs'); // 文件系統(tǒng)模塊
const path = require('path'); // 路徑模塊
const zlib = require('zlib'); // 壓縮模塊
const https = require('https'); // HTTPS模塊


// 所有平臺和二進制分發(fā)包的查找表
const BINARY_DISTRIBUTION_PACKAGES = {
  'darwin-x64': 'f_cli_darwin_x64',
  'darwin-arm64': 'f_cli_darwin_arm64',
  'win32-x64': 'f_cli_windows_x64',
}

// 調(diào)整你想要安裝的版本。也可以將其設(shè)置為動態(tài)的。
const BINARY_DISTRIBUTION_VERSION = '1.0.0';

// Windows平臺的二進制文件以.exe結(jié)尾,因此需要特殊處理。
const binaryName = process.platform === 'win32' ? 'f_cli.exe' : 'f_cli';

// 確定當前平臺的包名
const platformSpecificPackageName =
  BINARY_DISTRIBUTION_PACKAGES[`${process.platform}-${process.arch}`];

// 計算我們要生成的備用二進制文件的路徑
const fallbackBinaryPath = path.join(__dirname, binaryName);

// 創(chuàng)建HTTP請求的Promise函數(shù)
function makeRequest(url) {
  return new Promise((resolve, reject) => {
    https
      .get(url, (response) => {
        if (response.statusCode >= 200 && response.statusCode < 300) {
          const chunks = [];
          response.on('data', (chunk) => chunks.push(chunk));
          response.on('end', () => {
            resolve(Buffer.concat(chunks));
          });
        } else if (
          response.statusCode >= 300 &&
          response.statusCode < 400 &&
          response.headers.location
        ) {
          // 跟隨重定向
          makeRequest(response.headers.location).then(resolve, reject);
        } else {
          reject(
            new Error(
              `npm在下載包時返回狀態(tài)碼 ${response.statusCode}!`
            )
          );
        }
      })
      .on('error', (error) => {
        reject(error);
      });
  });
}

// 從tarball中提取文件的函數(shù)
function extractFileFromTarball(tarballBuffer, filepath) {
  let offset = 0
  while (offset < tarballBuffer.length) {
    const header = tarballBuffer.subarray(offset, offset + 512)
    offset += 512

    const fileName = header.toString('utf-8', 0, 100).replace(/\0.*/g, '')
    const fileSize = parseInt(header.toString('utf-8', 124, 136).replace(/\0.*/g, ''), 8)

    if (fileName === filepath) {
      return tarballBuffer.subarray(offset, offset + fileSize)
    }

    // 將offset固定到512的上限倍數(shù)
    offset = (offset + fileSize + 511) & ~511
  }
}

// 從Npm下載二進制文件的異步函數(shù)
async function downloadBinaryFromNpm() {
  // 下載正確二進制分發(fā)包的tarball
  const tarballDownloadBuffer = await makeRequest(
    `https://registry.npmjs.org/${platformSpecificPackageName}/-/${platformSpecificPackageName}-${BINARY_DISTRIBUTION_VERSION}.tgz`
  )

  const tarballBuffer = zlib.unzipSync(tarballDownloadBuffer)

  // 從軟件包中提取二進制文件并寫入磁盤
  fs.writeFileSync(
    fallbackBinaryPath,
    extractFileFromTarball(tarballBuffer, `package/bin/${binaryName}`),
    { mode: 0o755 } // 使二進制文件可執(zhí)行
  )
}

// 檢查是否已安裝平臺特定的軟件包
function isPlatformSpecificPackageInstalled() {
  try {
    // 如果optionalDependency未安裝,解析將失敗
    require.resolve(`${platformSpecificPackageName}/bin/${binaryName}`)
    return true
  } catch (e) {
    return false
  }
}

// 如果不支持當前平臺,拋出錯誤
if (!platformSpecificPackageName) {
  throw new Error('不支持的平臺!')
}

// 如果通過optionalDependencies已安裝二進制文件,則跳過下載
if (!isPlatformSpecificPackageInstalled()) {
  console.log('未找到平臺特定的軟件包。將手動下載二進制文件。')
  downloadBinaryFromNpm()
} else {
  console.log(
    '平臺特定的軟件包已安裝。將回退到手動下載二進制文件。'
  )
}

?

這段代碼的作用是根據(jù)當前的操作系統(tǒng)和架構(gòu),從 Npm 下載特定平臺的二進制文件,并將其寫入磁盤。

?

大部分的代碼都有注釋,具體的功能也一目了然,這里就不再過多解釋。我們挑幾個比較重要的點來說明一下。

  • BINARY_DISTRIBUTION_PACKAGES: 用于存儲所有平臺和二進制包的信息
  • 使用process.platform和process.arch用于確定符合當前工作環(huán)境的二進制包名稱
  • isPlatformSpecificPackageInstalled方法用于判斷是否根據(jù)optionalDependency安裝了指定的包,如果因為特殊原因沒安裝成功,我們就需要執(zhí)行手動下載操作(downloadBinaryFromNpm)

如果上述操作一切順利的話,我們就會在主包的根目錄下,按照了我們的二進制文件。

bin/cli

#!/usr/bin/env node

const path = require("path");
const childProcess = require("child_process");

// 存儲所有平臺和二進制分發(fā)包的查找表
const BINARY_DISTRIBUTION_PACKAGES = {
  'darwin-x64': 'f_cli_darwin_x64',
  'darwin-arm64': 'f_cli_darwin_arm64',
  'win32-x64': 'f_cli_windows_x64',
};

// Windows平臺的二進制文件以.exe結(jié)尾,因此需要特殊處理
const binaryName = process.platform === "win32" ? "f_cli.exe" : "f_cli";

// 確定此平臺的軟件包名稱
const platformSpecificPackageName =
  BINARY_DISTRIBUTION_PACKAGES[`${process.platform}-${process.arch}`]

function getBinaryPath() {
  try {
    // 如果optionalDependency未安裝,解析將失敗
    return require.resolve(`${platformSpecificPackageName}/bin/${binaryName}`);
  } catch (e) {
    // 如果未安裝,返回二進制文件的路徑
    return path.join(__dirname, "..", binaryName);
  }
}

// 使用child_process模塊執(zhí)行二進制文件并傳遞命令行參數(shù)
childProcess.execFileSync(getBinaryPath(), process.argv.slice(2), {
  stdio: "inherit",
});

上面的具體邏輯和我們install.js是類似的,都是基于process.platform和process.arch確定當前工作環(huán)境匹配的二進制源文件,并且執(zhí)行下載操作。

就像上面說的一樣,bin/cli這個方式是可以在命令行直接執(zhí)行的。npx f_cli_f create xxx。

有一個點還是忍不住的想介紹一下

  1. #!/usr/bin/env node 是一個稱為"shebang"的特殊注釋,通常出現(xiàn)在Unix或類Unix系統(tǒng)中的腳本文件的開頭。

這行代碼告訴操作系統(tǒng)使用/usr/bin/env來查找node命令,并使用它來解釋和執(zhí)行該腳本文件。這樣做的好處是,它允許腳本在不同的系統(tǒng)上找到正確的node解釋器,而不需要硬編碼node的路徑。

注意點

像使用bin/cli這種方式在命令行執(zhí)行命令時,有一點需要額外的注意。如果你當前工作環(huán)境中只有一個Node環(huán)境,因為我們cli中存在文件的寫入操作,此時在執(zhí)行命令時,會有一個寫入操作權(quán)限的錯誤警告。

其實這是一類錯誤,也就是npm在執(zhí)行時候需要sudo的操作權(quán)限。

圖片圖片

在stackoverflow中有很多關(guān)于npmthrowing error without sudo的解決方案[7]

其中一個高贊回答就是讓我們使用nvm等node版本管理工具。在之前我們寫過文章如何更優(yōu)雅的使用node版本管理工具 - fnm 高階版的nvm。

發(fā)布主包到npm

其實這步特別簡單就是兩個命令

  • npm login
  • npm publish

這樣我們所有的資源都上傳到npm了。然后,我們就可以通過我們熟悉的包管理器yarn/npm來安裝了。

額外說明

在上面的處理邏輯中我們只依據(jù)process.platfrom和process.arch做了最簡單的環(huán)境適配,其實這里還可以有很多的分支處理。

如果大家看過oxlint-npm的源碼[8]的話,它就對環(huán)境有很多的處理。

4. 本地應(yīng)用

在npm中我們已經(jīng)看到我們的cli已經(jīng)上傳成功了。

接下來,我們就可以利用yarn/npm等執(zhí)行下載操作了。

全局安裝

npm i -g f_cli_f

在控制臺中執(zhí)行上述操作,然后我們就將f_cli_f安裝到npm全局環(huán)境了。

我們可以通過npm list -g來查看是否在全局按照成功。

然后我們就可以下面的命令在本地使用我們的cli創(chuàng)建項目了。

f_cli_f create project

npx

除了全局安裝,我們也可以使用npx f_cli_f create project進行項目的初始化。

責任編輯:武曉燕 來源: 前端柒八九
相關(guān)推薦

2022-10-31 08:02:42

二進制計算乘法

2009-08-12 18:06:53

C#讀取二進制文件

2020-05-06 09:51:37

二進制Linux命令行工具

2009-12-16 10:49:42

Ruby操作二進制文件

2009-12-10 09:24:50

PHP函數(shù)fwrite

2023-09-18 23:50:25

二進制文件裁剪Layout

2022-11-18 10:17:01

2022-08-14 08:29:21

npmNode

2013-04-28 15:37:35

JBoss

2021-11-10 09:15:00

CPU01 二進制Linux

2009-11-02 11:27:42

VB.NET二進制文件

2009-02-27 09:37:33

Google二進制代碼

2018-10-22 14:37:16

二進制數(shù)據(jù)存儲

2020-05-22 18:00:26

Go二進制文件編程語言

2023-12-26 15:10:00

處理二進制文件

2010-06-09 13:02:29

MySQL啟用二進制日

2010-10-13 15:45:23

MySQL二進制日志

2023-03-20 08:24:31

工具GoReleaser

2017-04-11 10:48:53

JS二進制

2022-07-26 13:00:01

安全符號源代碼
點贊
收藏

51CTO技術(shù)棧公眾號

主站蜘蛛池模板: 日韩欧美在线视频观看 | 日韩福利 | 天堂视频免费 | 精品久久久久久久久久久 | 亚洲精品一区二三区不卡 | 日韩中文字幕在线播放 | 国产一区不卡在线观看 | 亚洲 一区| 日韩欧美一区二区三区四区 | 久日精品| 成人国内精品久久久久一区 | 国产精品高潮呻吟久久av野狼 | av在线免费观看不卡 | 欧美日韩一区二区三区四区 | 国产一区二区三区久久久久久久久 | 久久久国产精品视频 | 日韩精品一区二区三区中文在线 | 久久久久久久91 | 日本三级播放 | 国产欧美精品区一区二区三区 | 欧美jizzhd精品欧美巨大免费 | 超碰精品在线观看 | 黄视频网站免费观看 | 久久精品一区 | 91精品国产综合久久婷婷香蕉 | 精品国产精品一区二区夜夜嗨 | 97精品超碰一区二区三区 | 天天色图 | 色就干| 成人精品视频 | 伦理午夜电影免费观看 | 欧美一区二区三区久久精品 | 国产成人综合一区二区三区 | 可以免费观看的av片 | 久久精品一级 | 国产视频一区二区 | 91精品国产日韩91久久久久久 | 国产视频一区在线观看 | 国产三级电影网站 | 日韩精品在线网站 | 日韩中文在线 |