Day3 - 第一個 Node.js 專案

第一個專案

前言

花了兩天現在才正要準備進入建立第一個 Node.js 專案,但是我相信你對於 Node.js 的基本觀念都有一定的了解了,所以我們也可以比較放心的開始建立第一個 Node.js 專案。

基礎終端機指令

首先,我希望你先熟記幾個終端機指令,這些指令在實戰上都是非常常使用到的,甚至你在使用 VSCode 的終端機時,也會使用到這些指令,在文章中,我極有可能會使用到這些指令,因此我希望你至少知道這些指令

  • cd [路徑]:移動到指定路徑
    • ex: cd /Users/username/Desktop:移動到桌面
  • cd ..:回到上一層資料夾
  • mkdir [資料夾名稱]:建立資料夾
    • ex: mkdir my-project:建立 my-project 資料夾
  • touch [檔案名稱(副檔名)]:建立檔案
    • ex: touch index.js:建立 index.js 檔案
    • 需注意 Windows 系統不支援此指令,請使用 echo > [檔案名稱(副檔名)] 代替
  • ls:列出當前路徑下的所有檔案與資料夾
  • ll:列出當前路徑下的所有檔案與資料夾(包含詳細資訊)

以上都是最基本、最常見的終端機指令,如果你還不熟悉的話,我會建議你要嘗試去使用並習慣這些指令,因為這些指令在日後的開發上都是非常常用的。

Note
副檔名意指檔案的類型,例如:index.js 的副檔名為 .js,主檔名為 index,通常作業系統會特別隱藏副檔名,所以如果看不到副檔名的話,你必須特別去作業系統的設定中開啟才看得到。

先聊聊 Package.json 吧?

為了讓你可以更習慣終端機的操作,我會希望你試著使用終端機來輸入以下指令

1
mkdir example

接著請你移動到 example 資料夾中

1
cd example

接著請你在 example 這個資料夾下輸入以下指令

1
npm init -y

這時候你用 VSCode 打開這個專案時,應該會長這個樣子

package.json

Note
npm init -y 是一個快速建立 package.json 的指令。

那麼以上這些行為,我們通常稱之為「初始化專案」(通常還會有 git init,但這不是這一篇主軸,因此就沒特別介紹),也就是說我們透過 npm init 來初始化專案,這樣子稍後我們才可以安裝套件,為什麼呢?因為我們所有的套件資訊都是寫在 package.json 中,因此我們必須先初始化專案,才能夠安裝套件。

那麼我們可以看到畫面上有一個 package.json 的檔案,打開後你會看到以下內容

1
2
3
4
5
6
7
8
9
10
11
12
{
"name": "example",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}

如同前面所言,這個 package.json 會是我們管理套件的地方,因此我們可以透過 package.json 來安裝套件,那麼我們就來安裝第一個套件吧!

首先我們先安裝一個套件,也就是 nodemon,這個套件可以幫助我們在修改程式碼時,自動重新執行程式碼,這樣我們就不用每次都要重新執行程式碼了。

1
npm install nodemon

安裝完畢後,你會發現 package.json 內容變成以下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"name": "example",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"nodemon": "^3.0.1"
}
}

我們可以看到 nodemon 包含了版本被記錄到了 package.json 中,而且你也會發現你的專案多了兩個東西,分別是 node_modules、package-lock.json,這兩個東西是什麼呢?後面我們會再來介紹,先回過頭來看 package.json 每一個屬性。

剛才有提到 package.json 是用來管理套件的地方,說它是 NPM 的核心也不為過,因為我們可以透過 package.json 來安裝、更新、移除套件,因此這個檔案是非常重要的。

底下我也針對當前 package.json 中的每一個屬性做了一些簡單的說明:

  • name:專案名稱
  • version:專案版本
  • description:專案描述
  • main:專案的進入點
  • scripts:專案的指令
  • keywords:專案的關鍵字
  • author:專案作者
  • license:專案授權
  • dependencies:專案的相依套件
  • devDependencies:專案的開發相依套件

Note
實際上 package.json 並不只有這些屬性,如果想知道還有哪些特殊屬性的話,建議直接觀看 npm package.json 官方文件,因為實在太多了。

那麼這邊你會比較需要注意的屬性有三個,分別是 dependenciesdevDependenciesscripts,因為這三個屬性你是一定會使用到的,因此我們就來看看這三個屬性的用途。

首先先講 scripts 屬性,這個屬性是專門來放可執行的指令,例如:我們可以在 scripts 中放入 start 指令,這樣我們就可以透過 npm start 來執行指令,那該怎麼寫呢?這邊讓我示範一下

1
2
3
4
5
6
7
{
// ...省略其他屬性
"scripts": {
"start": "node index.js"
},
// ...省略其他屬性
}

當然,這個指令可以隨著你的需求而變化,例如:你可以透過 npm run dev 來執行 nodemon index.js,這樣你就可以透過 npm run dev 來執行 nodemon index.js

1
2
3
4
5
6
7
8
{
// ...省略其他屬性
"scripts": {
"start": "node index.js",
"dev": "nodemon index.js"
},
// ...省略其他屬性
}

所以這邊就算你要命名為「亞古獸進化」也是完全沒有問題的

1
2
3
4
5
6
7
8
{
// ...省略其他屬性
"scripts": {
"start": "node index.js",
"亞古獸進化": "nodemon index.js"
},
// ...省略其他屬性
}

只是你在輸入指令時,就必須輸入 npm run 亞古獸進化

亞古獸進化

這時候你可能會好奇了…

「為什麼前面 start 是輸入 npm startdev 卻是輸入 npm run dev?」

主要原因是因為 npm 本身有提供一些快捷指令,這些快捷指令是不需要額外輸入 run 的,例如:npm startnpm test,而 npm run dev 則是我們自己定義的指令,因此需要額外輸入 run

那麼有哪些快捷指令呢?這邊我也簡單列一下實戰上常用的指令

  • npm start:執行 npm run start
  • npm test:執行 npm run test

這三個基本上是最常用到的快捷指令。

這邊也額外分享一個小技巧,有時候我們一個專案內會有很多 scripts,但我們並不想要打開 package.json 來查看有哪些指令,那麼這時候你可以透過以下指令來查看

1
npm run

npm run

這樣就可以列出所有的 scripts 了。

到目前為止,你以為 scripts 就到這邊結束了嗎?不,其實 scripts 有所謂的「Life Cycle Operation Order」,也就是說 scripts 會依照一定的順序來執行,那麼這個順序是什麼呢?這邊我也列出來給你參考

當我們執行 npm run build 的時候,如果你在 scripts 中有定義 prebuildbuildpostbuild 這三個指令,那麼執行順序會是 prebuild -> build -> postbuild,也就是說 prebuild 會在 build 之前執行,postbuild 會在 build 之後執行。

這邊我也簡單示範寫一下:

1
2
3
4
5
6
7
8
9
{
// ...省略其他屬性
"scripts": {
"prebuild": "echo \"prebuild\"",
"build": "echo \"build\"",
"postbuild": "echo \"postbuild\""
},
// ...省略其他屬性
}

Life Cycle

Note
echo 是一個可以在終端機中顯示文字的指令,例如:echo "Hello World",這樣就會在終端機中顯示 Hello World

那麼這個「Life Cycle Operation Order」有什麼用呢?舉例來講,如果你期望在 Build 之前先做什麼事情,那麼就可以使用 「pre-」與 「post-」 來定義指令,這個又稱之為「pre-」和「post-」事件,但以實戰來講會比較常見使用 postinstall,也就是在執行套件安裝(npm install)之後要做什麼事情,像是我之前寫的「使用 Swagger 自動生成 API 文件」文章會需要在安裝完畢後,自動生成 API 文件,這時候就可以使用 postinstall 來定義指令,例如…

1
2
3
4
5
6
7
{
// ...省略其他屬性
"scripts": {
"postinstall": "node ./swagger.js"
},
// ...省略其他屬性
}

當然 scripts 還可以這樣子寫:

1
2
3
4
5
6
7
8
{
// ...省略其他屬性
"scripts": {
"postinstall": "npm run swagger",
"swagger-autogen": "node ./swagger.js"
},
// ...省略其他屬性
}

如果有多個指令的話,你可以善加利用 && 來執行多個指令,例如:

1
2
3
4
5
6
7
8
9
{
// ...省略其他屬性
"scripts": {
"postinstall": "npm run swagger && npm run gulp",
"swagger-autogen": "node ./swagger.js",
"gulp": "echo \"gulp\""
},
// ...省略其他屬性
}

這樣子就可以依序執行 swagger-autogengulp 了。

其餘的部分,由於 scripts 實在太多了,因此我就不一一介紹了,如果你想要知道更多的 scripts 的話,建議直接觀看 npm scripts 官方文件

package-lock.json 又是什麼?

想不到吧?光是 package.json 就有好多東西可以講,那…package-lock.json 呢?這個檔案又是什麼呢?

首先 package-lock.json 只有在你輸入 npm install 時才會產生,而這個檔案主要是用來詳細記錄該套件的版本資訊,這樣的好處是當你在不同的環境下(例如:不同的電腦)執行 npm install 時,會依照 package-lock.json 中的版本來安裝相關套件,這樣就可以避免不同環境下安裝不同版本的套件,造成專案無法正常運作的問題。

但是問題來了,我們剛剛前面有說到,我們的套件在安裝的時候會依據「package.json」中的版本來安裝,那麼為什麼還需要 package-lock.json 呢?這樣子不是很多此一舉嗎?

這邊讓我們看一下實際範例或許會比較清楚一點,剛才前面我們有輸入 npm install nodemon 安裝了 nodemon 套件,目前 package.json 是長這樣子

1
2
3
4
5
6
7
{
"name": "example",
// ...省略其他屬性
"dependencies": {
"nodemon": "^3.0.1"
}
}

但是當你打開 package-lock.json 時,你會發現 package-lock.json 超級長,但我們重點是找到 nodemon

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
{
"name": "example",
"version": "1.0.0",
"lockfileVersion": 2,
"requires": true,
// ...省略其他屬性
"dependencies": {
// ...省略其他屬性
"nodemon": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.0.1.tgz",
"integrity": "sha512-g9AZ7HmkhQkqXkRc20w+ZfQ73cHLbE8hnPbtaFbFtCumZsjyMhKk9LajQ07U5Ux28lvFjZ5X7HvWR1xzU8jHVw==",
"requires": {
"chokidar": "^3.5.2",
"debug": "^3.2.7",
"ignore-by-default": "^1.0.1",
"minimatch": "^3.1.2",
"pstree.remy": "^1.1.8",
"semver": "^7.5.3",
"simple-update-notifier": "^2.0.0",
"supports-color": "^5.5.0",
"touch": "^3.1.0",
"undefsafe": "^2.0.5"
}
},
// ...省略其他屬性
}
}

你會發現 nodemon 底下還記錄了其他套件的版本,這些套件都是 nodemon 的相依套件,因此當你在不同的環境下執行 npm install 時,會依照 package-lock.json 中的版本來安裝相關套件,這樣就可以避免不同環境下安裝不同版本的套件,造成專案無法正常運作的問題。

因此 package-lock.json 是一個非常重要的檔案哩~

node_modules 呢?

node_modules 呢…

node_modules

就是一個黑洞,就這樣 End。

其實 node_modules 一直被戲稱為黑洞不是沒有原因的,因為每次安裝套件時,都會在 node_modules 中產生資料夾,而這個資料夾中會包含該套件的所有檔案,因此當你安裝了很多套件時,node_modules 會變得非常大,甚至會超過 100MB(我目前最高紀錄是 500MB 左右),這時候你就會發現 node_modules 就像一個黑洞一樣,因為你根本不知道裡面有什麼東西,只知道它越來越肥大。

因此你大概只需要知道它是一個放置我們將要使用的套件的地方就好了,至於裡面有什麼東西,你真的不用太在意。

如果你很在意的話,你倒是可以考慮改用別的套件管理工具,例如:pnpm,這個套件管理工具可以讓你的 node_modules 變得非常小,因為它會將相同的套件共用,因此你的 node_modules 會變得非常小,但是這邊我就不多做介紹了,如果介紹這個的話,應該可以在寫個三天吧。

建立第一個 Node.js 專案

天啊!講了一大推廢話,我們終於要建立一個 Node.js 專案了,一開始就先讓我們來建立一個簡單的 Node.js 專案吧!

首先 Node.js 本身內建許多模組,那麼因為 JavaScript 是一個非常強大的語言,因此我們可以透過 JavaScript 來建立一個簡單的伺服器,這邊我們就來建立一個簡單的伺服器吧!

請你在終端機中輸入以下指令(請在我們剛剛建立的 example 資料夾中輸入)

1
touch index.js

接著由於 Node.js 官方文件中有提供一個簡單的範例,這邊我就直接拿來使用了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const http = require('node:http');

const hostname = '127.0.0.1';
const port = 3000;

const server = http.createServer((req, res) => {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
res.end('Hello, World!\n');
});

server.listen(port, hostname, () => {
console.log(`Server running at http://${hostname}:${port}/`);
});

接下來打開 package.json,在 scripts 新增 start 指令

1
2
3
4
5
6
7
{
// ...省略其他屬性
"scripts": {
"start": "node index.js"
},
// ...省略其他屬性
}

最後你就可以打開終端機輸入 npm start 來執行指令,接著你就可以在瀏覽器上輸入 http://localhost:3000 來看到結果囉!

那麼這一篇稍微有一點長了,下一篇我們再來針對 Node.js 的模組做一些介紹吧!

碎碎念

說真的,不得不說 Raycast 真的很好用,我要上傳圖片之前,可以先透過 Raycast 壓縮完畢後再上傳,真心滿推薦給每一位 Mac 開發者的~

(絕對比內建 Spotlight 好用)