# 應用程式打包

為了減少圍繞著 Windows 上長路徑名稱問題的 [issues](https://github.com/joyent/node/issues/6960) ，稍微地加速 `require` 和隱藏你的原始碼避免不小心被看到，你可以選擇把你的應用程式打包成一個 [asar][asar] 壓縮檔，只需要改變一點點你的原始碼就好。
## 產生 `asar` 壓縮檔

一個 [asar][asar] 壓縮檔是一個簡單的類 tar 格式的檔案，它會把幾個檔案串接成一個檔案， Electron 可以不需要解壓縮整個檔案就從中讀取任意檔案。

把你的應用程式打包成 `asar` 壓縮檔的步驟：

### 1. 安裝 asar 工具包

```bash
$ npm install -g asar
```

### 2. 用 `asar pack` 打包

```bash
$ asar pack your-app app.asar
```

## 使用 `asar` 壓縮檔

在 Electron 中有兩組 API：Node.js 提供的 Node APIs 和 Chromium 提供的 Web
APIs，兩組 API 都支援從 `asar` 壓縮檔中讀取檔案。 

### Node API

因為 Electron 中有一些特別的補釘，像是 `fs.readFile` 和 `require` 這樣的 Node API 會將 `asar` 壓縮檔視為許多虛擬目錄，將裡頭的檔案視為在檔案系統上的一般檔案。

例如，假設我們有一個 `example.asar` 壓縮檔在 `/path/to` 中:

```bash
$ asar list /path/to/example.asar
/app.js
/file.txt
/dir/module.js
/static/index.html
/static/main.css
/static/jquery.min.js
```

讀取一個在 `asar` 壓縮檔中的檔案：

```javascript
const fs = require('fs')
fs.readFileSync('/path/to/example.asar/file.txt')
```

列出所有在壓縮檔根目錄下的檔案：

```javascript
const fs = require('fs')
fs.readdirSync('/path/to/example.asar')
```

使用一個壓縮檔中的模組：

```javascript
require('/path/to/example.asar/dir/module.js')
```

你也可以利用 `BrowserWindow` 在 `asar` 壓縮檔中呈現一個網頁：

```javascript
const BrowserWindow = require('electron').BrowserWindow
var win = new BrowserWindow({width: 800, height: 600})
win.loadURL('file:///path/to/example.asar/static/index.html')
```

### Web API

在一個網頁中，壓縮檔中的檔案都可以透過 `file:` 這個協定被存取，如同 Node API，`asar` 壓縮檔都被視為目錄。

例如，要透過 `$.get` 取得一個檔案：

```html
<script>
var $ = require('./jquery.min.js');
$.get('file:///path/to/example.asar/file.txt', function(data) {
  console.log(data);
});
</script>
```

### 把一個 `asar` 壓縮檔視為一般檔案

在一些像是驗證 `asar` 壓縮檔檢查碼(checksum)的例子中，我們需要以檔案的方式讀取 `asar` 壓縮檔中的內容，為了達到這個目的，你可以使用內建的
`original-fs` 模組，它提供了沒有 `asar` 支援的原生 `fs` API:

```javascript
var originalFs = require('original-fs')
originalFs.readFileSync('/path/to/example.asar')
```

你也可以設定 `process.noAsar` 為 `true` 來關掉在 `fs` 模組中的 `asar` 支援：

```javascript
process.noAsar = true
fs.readFileSync('/path/to/example.asar')
```

## Node API 上的限制

儘管我們盡可能的努力嘗試著使 Node API 中的 `asar` 壓縮檔像目錄一樣運作，還是有一些基於 Node API 低階本質的限制存在。 

### 壓縮檔都是唯讀的

所有壓縮檔都無法被修改，因此所有可以修改檔案的 Node API 都無法與 `asar ` 壓縮檔一起運作。

### 使用中的目錄無法被設為壓縮檔中的目錄

儘管 `asar` 壓縮檔被視為目錄，卻並沒有真正的目錄在檔案系統中，所以你永遠無法將使用中的目錄設定成 `asar` 壓縮檔中的目錄，把他們以 `cwd` 選項的方式傳遞，對某些 API 也會造成錯誤。

### 更多透過 API 拆封的方法

大部分 `fs` API 可以讀取一個檔案，或是不用拆封就從 `asar` 壓縮檔中取得一個檔案的資訊，但對一些需要傳遞真實檔案路徑給現行系統呼叫的 API ，Electron 將會解開需要的檔案到一個暫時的檔案，然後傳遞該暫時檔案的路徑給那些 API 以便使他們可以運作，這會增加這些 API 一點負擔。


需要額外拆封的 API ：

* `child_process.execFile`
* `child_process.execFileSync`
* `fs.open`
* `fs.openSync`
* `process.dlopen` - 在原生模組中被 `require` 使用

### `fs.stat` 的不真實的狀態資訊

`fs.stat` 回傳的 `Stats` 物件和它在 `asar` 壓縮檔中的檔案朋友都是以猜測的方式產生的，因為那些檔案不存在檔案系統，所以你不應該信任 `Stats` 物件，除了取得檔案大小和確認檔案型態之外。

### 執行 `asar` 壓縮檔中的二進位檔

有像是 `child_process.exec`、`child_process.spawn` 和 `child_process.execFile` 的 Node APIs 可以執行二進位檔，但只有 `execFile` 是 `asar` 壓縮檔中可以執行二進位檔的。

這是因為 `exec` 和 `spawn` 接受的輸入是 `command` 而不是 `file`，而 `command` 們都是在 shell 底下執行，我們找不到可靠的方法來決定是否一個命令使用一個在 `asar` 壓縮檔中的檔案，而儘管我們找得到，我們也無法確定是否我們可以在沒有外部影響(side effect)的情況下替換掉命令中的路徑。

## 加入拆封檔案到 `asar` 壓縮檔中

如前述，一些 Node API 再被呼叫時會拆封檔案到檔案系統，除了效能議題外，它也會導致掃毒軟體發出 false alerts。

要繞過這個問題，你可以透過使用 `--unpack` 選向來拆封一些建立壓縮檔的檔案，以下是一個不包含共享原生模組的函式庫的例子：

```bash
$ asar pack app app.asar --unpack *.node
```

執行這個命令以後，除了 `app.asar` 以外，還有一個帶有拆封檔案的 `app.asar.unpacked` 資料夾被產生出來，當你要發布給使用者時，你應該把它和 `app.asar` 一起複。

[asar]: https://github.com/atom/asar
