# Node.js 快速入門
:::warning
- 本篇筆記不包含 Javascript 基礎語法
- 只有簡短的介紹 Javascript 模組相關的語法
- 關於 Javascript 語法,可以參考
- [W3 Schools - JavaScript Tutorial](https://www.w3schools.com/js/)
- [JS 語法簡介](/@PinJhih/js_basic)
:::
- Node.js 是一個 **伺服器端** 的 **Javascript 執行環境**
- 安裝 Node.js 後,可以直接在電腦上執行 JS 程式碼
- **不需要透過瀏覽器執行**、程式的功能**不被侷限於瀏覽器中**
- 可以存取瀏覽器以外的系統資源,例如**檔案**、**資料庫**和**其他軟體**
- 透過 Node.js,可以開發、架設許多 Web 應用程式
- 搭配一些套件,可以用 JS 開發電腦上的應用程式
- 除了執行環境,Node.js 中還包含許多其他的開發資源,包含 ...
- 豐富的 **內建函式庫和套件**
- **Node.js 套件管理工具 - NPM**
- Node Package Manager: Node.js 預設的套件管理工具
- 用來管理 Node.js 專案,以及專案中使用到的所有套件
- 大量的 **第三方套件**
- 公開於官方的網站上,可以透過 NPM 下載、安裝
## 安裝與使用
### 安裝 Node.js
**Windows 及 Mac**
- 在 [**官方下載頁面**](https://nodejs.org/zh-tw/download) 選擇對應你電腦作業系統的版本
- 執行安裝程式後即可使用

**Linux**
- Debian, Ubuntu ...
```
sudo apt update
sudo apt install nodejs
```
使用其他 Linux 發行版或作業系統,可以參考官方文件 [Installing Node.js via package manager](https://nodejs.org/en/download/package-manager)
:::success
**確認安裝結果**
- 打開任意的終端機 (Windows 上的 PowerShell, cmd ...),執行以下指令,可以確認是否安裝成功
```
node -v
```
- 若安裝成功,會顯示在你的電腦上目前安裝的 Node.js 的版本
- 舉例來說,如果安裝的版本是 18.17.1,會顯示
```
v18.17.1
```
- 只要顯示類似的內容就表示安裝成功
:::
### 使用 Node.js
- 進入 Node.js 環境
- 在終端機輸入 `node` 指令,可以打開互動式的執行環境
- 在此環境下,可以執行輸入並執行 JS 程式碼,而不需要先寫好程式碼檔案
```
node
```
- 執行 *.js* 檔案
- 在 `node` 指令後加上要執行的 JS 程式碼檔案
```
node <path_of_your_script>
```
完整使用方式可以參考[官方文件](https://nodejs.org/api/synopsis.html#usage)
### 在 VS Code 中使用 Node.js
- VS Code 本身就支援 Javascript 語法提示,不需要另外安裝 JS 的套件
- 如果想要不下指令、直接透過 UI 介面執行程式碼檔案,可以安裝擴充套件 **Code Runner**

- 安裝 Code Runner 後,點擊右上角 "Run Code" 的按鈕,就可以執行程式碼檔案

## Node.js 中的 Module
:::success
**Module** (模組)
- 一個模組是 **一段程式碼**,且具有以下特性的
- 內容通常有高度的 **相關性**
- 一個模組的功能通常 **只和一個目的有關**
- 比如: 用來處理檔案的模組、處理字串的模組、進行網路傳輸的模組
- 經過適當的**封裝** (encapsulated)
- 可以被其他人或其他程式碼檔案,直接引入、使用
- 可以是 **一個 Javascript 程式碼檔案** 或 **一個包含多個檔案的資料夾**
- 通常一個模組**只會在它所屬的專案中使用**
- 一個專案的程式碼可以被分開寫成多個模組
- 專案 A 不會使用專案 B 的模組
<!-- - 從程式碼的角度是可以的
- 但是從專案結構和慣例上來說,這是很糟糕的作法 -->
:::
:::warning
**Package** (套件)
- 一個 **資料夾**,通常 **包含多個模組**
- 此資料夾必須是一個 **Node.js 專案**
- 需要有 NPM 的專案設定檔 *packages.json*
- 當一個專案成為 package,它就 **可以被其他專案使用**
- 專案 A 是一個 package,提供一些檔案處理的模組
- 專案 B 是一個應用程式,B 可以透過 NPM 安裝專案 A,並使用專案 A 提供的模組
- 詳細內容可以參考
- [NPM 官方文件](https://docs.npmjs.com/packages-and-modules/contributing-packages-to-the-registry)
- [這篇文章](https://www.freecodecamp.org/news/how-to-create-and-publish-your-first-npm-package/)
:::
:::info
**Module Specifications**
- 建立 JS 模組的標準 (格式)
- 有 **CommonJS** 和 **ES** 兩種常見的標準
- Node.js 預設使用 **CommonJS**,所以本篇以 **CommonJS** 為主
- 詳細差異可以參考 [CommonJS vs ES](https://pjchender.dev/nodejs/node-module-system/)
:::
關於模組、套件的詳細內容,可以參考 [**官方文件**](https://nodejs.org/api/packages.html)
### 建立 Module (單檔案)
- 在程式碼中,一個 Module 會以 **物件** 的形式存在
- 在 CommonJS 標準中,修改 **`module.exports`** 屬性可以把某個物件 **匯出**
- 等號右邊的物件就是代表這個模組的物件
```javascript
module.exports = obj_of_module;
```
- 把上面模組稱為 module A,另一個模組稱為 module B
- 當 module B 引入(使用了) module A,module A 會拿到一個和 `obj_of_module` 一樣的物件
- 只要是 **物件就可以匯出成一個模組**,所以可以有多種不同的寫法
- 關於 JS 的物件,可以參考
- [這篇筆記](https://hackmd.io/1GUTx2PvQeOmXHBaRajpgw?view#Classes-amp-Objects)
- [這篇文件](https://developer.mozilla.org/zh-TW/docs/Learn/JavaScript/Objects/Basics)
:::success
下面以建立 **一個提供數學計算的 module** 為例子
- 開啟一個檔案,命名為 *calculator.js*
```javascript
// calculator.js
function add(a, b) {
return a + b;
}
function product(a, b) {
return a * b;
}
const pi = 3.14159265;
const e = 2.71828183;
// 將以上兩個 function 匯出
module.exports = { add, product, pi, e };
// 要注意不要在 function 名稱後面加上 ()
// 因為 add() 是這個 function 的執行結果
// 而不是 function 本人
```
:::
:::info
- 其他等效的寫法
```javascript
// 前略 ...
var myModule = { add, product };
module.export = myModule;
```
```javascript
var myModule = {
add: function (a, b) {
return a + b;
},
product: function (a, b) {
return a * b;
},
pi: 3.14159265,
e: 2.71828183,
};
module.exports = myModule;
```
```javascript
var myModule = {};
myModule.add = function (a, b) {
return a + b;
};
myModule.product = function (a, b) {
return a * b;
};
myModule.pi = 3.14159265;
myModule.e = 2.71828183;
module.exports = myModule;
```
> 還有很多不同的寫法,只要符合 JS 物件的語法即可
:::
### 使用 Modules
- 呼叫 `require()`,可以在目前的檔案中引入其他模組
- 參數是模組的檔案路徑
- `require()` 會回傳其他模組匯出的物件
- 通常會用一個變數把這個物件儲存起來
```js
var module = require("path/to/the/module");
```
:::success
以剛剛建立的 *calculator* 模組為例
- 先建立一個新的檔案 *index.js*,檔案放在相同的資料夾中,並把要執行的程式寫在裡面
```javascript
// index.js
// 變數可以不用和模組名稱相同
// "./calculator" 是相對路徑 表示目前目錄底下的 calculator 模組
var cal = require("./calculator");
// 引入 .js 檔,.js 可以省略
// 所以上面那行也可以寫成 var cal = require("./calculator.js");
// 呼叫 calculator 模組的 add
console.log(cal.add(1, 2));
```
**Output**
```
3
```
:::
:::info
可以用 `console.log()` 把 `require()` 拿到的物件印出來
- 有時候物件內容較多,只會顯示 `[object Object]`
- 可以搭配 `JSON.stringify()` 把物件完整的印出來
```javascript
// index.js
var cal = require("./calculator");
console.log(cal);
// 或是 console.log(JSON.stringify(cal));
```
- **Output**
```json
{
add: [Function (anonymous)],
product: [Function (anonymous)],
pi: 3.14159265,
e: 2.71828183
}
```
- 結構和 calculator.js 中,匯出的物件相同
:::
### 建立 Module (多檔案)
- 一個資料夾中的多個檔案 (單檔案的模組),可以透過一些設定集結成一個模組
- 在資料夾中建立 *index.js* 檔案
- 在該檔案中匯出的物件,是代表整個資料夾物件
- 只要 `require()` 資料夾名稱,就可以拿到 *index.js* 匯出的物件
- 其他模組只要一行 `require()` 就可以使用整個資料夾中的所有模組
:::success
以剛才的例子,我們可以把 *calculator* 細分成 *operation* 和 *constant* 兩個小模組
- 先建立一個資料夾 *calculator*,在裡面建立三個檔案 *operation.js*, *constant.js* 和
- 檔案結構如下
```
.
└── calculator/
├── constant.js
├── operation.js
└── index.js
```
- 在 *calculator/constant.js* 加入以下內容
```javascript
// calculator/constant.js
const pi = 3.14159265;
const e = 2.71828183;
module.exports = { pi, e };
```
- 在 *calculator/operation.js* 加入以下內容
```javascript
// calculator/operation.js
function add(a, b) {
return a + b;
}
function product(a, b) {
return a * b;
}
module.exports = { add, product };
```
- 在 *calculator/index.js* 中,引入兩個模組,整合成一個物件並匯出
- 在 index.js 中,可以依自己的需求定義要匯出的物件,不一定要按照下面的方式
- 下面舉例的是最簡單的方式
- 使用這個模組時,會拿到一個物件,包含這個資料夾中的所有模組
```javascript
// calculator/index.js
var constant = require("./constant");
var operation = require("./operation");
module.exports = { constant, operation };
```
- 在 *calculator* 外面一層的資料夾建立一個 *index.js* 檔案,在這個檔案中編寫要執行的程式內容
- 現在的檔案結構應該如下
```
.
├── calculator/
│ ├── constant.js
│ ├── index.js
│ └── operation.js
└── index.js
```
- 在剛剛建立 *index.js* 中加入以下內容
```javascript
// index.js
// 引入 calculator 模組
var calculator = require("./calculator");
console.log("PI =", calculator.constant.pi);
console.log("1 + 1 =", calculator.operation.add(1, 1));
```
**Output**
```
PI = 3.14159265
1 + 1 = 2
```
:::
:::info
這邊一樣可以把 `require()` 回傳的物件印出來
```javascript
// index.js
var cal = require("./calculator");
console.log(cal);
```
- 在目前的情況下,calculator 模組是這樣的結構
```json
{
constant: { pi: 3.14159265, e: 2.71828183 },
operation: { add: [Function: add], product: [Function: product] }
}
```
- 包含兩個屬性 `constant` 和 `operation`
- `constant` 又有兩個屬性 `pi`, `e`
- `operation` 又有兩個屬性 `add`, `product`
- 所以 `cal.operation.add` 就會對應 *calculator/operation.js* 中的 function `add`
:::
## NPM
- Node Package Manager
- 用來 **管理 Node.js 套件** 的軟體工具
- 管理套件的 **相關資訊**
- 名稱、作者、版本號碼、授權方式 ... 等
- 管理 **Dependency**
- 一個套件可以需要使用另一個套件
- NPM 會自動安裝必要的其他套件
- **安裝套件**
- **發布套件**
- 將自己的套件公布於 NPM 官網上供人下載
- 通常來說,**Node.js 專案** 會以 **NPM 套件** (專案) 的形式建構
- 不需要 NPM 還是可以執行自己寫的程式碼、使用 Node.js 原生的模組
- 但有了 NPM 才能正確的安裝、使用外部的套件
NPM 的完整使用方式可以參考 [NPM 官方文件]((https://docs.npmjs.com/))
:::info
**NPM 套件**
- 一個資料夾中只要有 ***package.json* 檔案**,這個資料夾就是一個 NPM 專案
- *package.json* 是 NPM **專案的設定黨**,包含所有和這個專案有關的資訊
- *package.json* 可以手動建立,下面是一個範例的內容
- 也可以透過 `npm` 指令建立
:::
### `npm` 指令
- 安裝 Node.js 時,就會一起安裝好 NPM,NPM 的指令是 `npm`
- 在終端機執行 `npm` 可以拿到使用簡介
```bash
npm
```
**Output**
```
npm <command>
Usage:
npm install install all the dependencies in your project
npm install <foo> add the <foo> dependency to your project
npm test run this project's tests
npm run <foo> run the script named <foo>
npm <command> -h quick help on <command>
npm -l display usage info for all commands
npm help <term> search for help on <term> (in a browser)
npm help npm more involved overview (in a browser)
All commands:
...
```
- 完整的 `npm` 指令使用方式,可以參考 [官方文件 - CLI Commands](https://docs.npmjs.com/cli/v9/commands)
### 建立 NPM Project
- 執行 `npm init` 可以自動建立標準格式的 *package.json* 檔案
```
npm init
```
- 過程中,`npm` 會一項一項詢問有關專案的資訊
- 這些資訊可以在之後修改 (直接修改 *package.json*)
- 這些資訊和程式碼的執行無關,依照自己的需求和喜好設定即可 (也可以全部使用預設數值)
- 在每一個詢問的項目中
- 如果要使用預設的數值,不要打任何文字直接按 enter,進入下一項設定
- 如果要使用自己的數值,直接輸入你要設定的資訊,按 enter 進入下一項設定
- 詳細使用方式可以參考 [這篇文章](https://ithelp.ithome.com.tw/articles/10191682)
:::success
成功後,NPM 就會在目前的資料夾中建立 *package.json* 檔案
:::
### 安裝 Packages
- **Local install**
- 這個情況下,套件只會 **安裝在目前的專案中**
- 套件的相關檔案會被放在 ***node_modules* 資料夾**
- 其他專案如果也要用相同的套件,就需要在那個專案中也安裝一次
- 套件的相關訊息會被記錄在 *package.json* 中
- 一般的 **函式庫** (library) 套件通常用 local install
- 指要在自己的程式碼中呼叫、使用的套件
```bash
npm install <package_name>
```
- **Global install**
- 套件會安裝在 NPM 在你的系統中的目錄
- 在電腦上,**任何的專案或目錄都可以使用這個套件**
- 通常用來安裝一些 **工具類** 的套件
- 指一些用 Node.js 開發的程式
- 因為應用程式應該要能在電腦上的任何位置使用,所以才選用 global install
```bash
npm install -g <package_name>
```
- **Install Dependencies**
- 因為 local install 之後,外部套件的檔案會被放在專案的資料夾中
- 會讓專案的總檔案大小變大
- 所以在保存、傳輸或是 **初始化專案** 原始碼時,通常不會包含 *node_modules*
- 也就是專案中沒有 *node_modules* (所以也不會有外部套件的檔案)
- 但是專案中會用到的套件都記錄在 *package.json*
- 這種情況通常發生在
- 從 Github 或其他儲存庫 (repository) **下載專案程式碼** 時 (通常 *node_modules* 不會放上儲存庫)
- 透過 **專案產生器** 產生初始專案時 (產生器通常也不會包含其他套件的程式碼)
- 執行 `npm install`,就會根據 *package.json* 中的設定,自動安裝所有必要的套件
```
npm install
```
- 更多內容可以參考
- [官方文件](https://docs.npmjs.com/cli/v8/commands/npm-install)
- [這篇文章](https://linyencheng.github.io/2022/09/27/relationships-between-frontend-and-backend/tool-npm/#npm-install-%E5%AE%89%E8%A3%9D-node-module-%E7%9A%84%E8%AA%9E%E6%B3%95)
:::info
- 使用 `npm` 安裝套件,其實不需要先執行 `npm init`
- 執行 `npm install <package_name>` 時,就會建立一個沒有專案資訊的 *package.json*
- *package.json* 會只有 dependency 的相關資訊
:::
### 移除 Packages
使用方式和安裝時類似,只要把 `install` 改成 `uninstall` 就可以了
```
npm uninstall <package_name>
```
### package.json
筆記待補
- 可以先參考
- [NPM 官方文件](https://docs.npmjs.com/cli/v9/configuring-npm/package-json)
- [Node.js 官方文件](https://nodejs.org/api/packages.html#nodejs-packagejson-field-definitions)
### 自訂指令
筆記待補
- 可以先參考
- [官方文件](https://docs.npmjs.com/cli/v9/using-npm/scripts)
- [這篇文章](http://jamestw.logdown.com/posts/1378697-egghead-how-to-use-npm-scripts-as-your-build-tool)