# 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) 選擇對應你電腦作業系統的版本 - 執行安裝程式後即可使用 ![](https://hackmd.io/_uploads/BJNchp0an.png) **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** ![](https://hackmd.io/_uploads/HJmGCb06h.png) - 安裝 Code Runner 後,點擊右上角 "Run Code" 的按鈕,就可以執行程式碼檔案 ![](https://hackmd.io/_uploads/H1SdRb0a3.png) ## 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)