Home
Explore Uedu
Student Console
Register as Member/Login
Research Informed Consent Center
Survey Center
Educational Big Data Challenge
Claim or create a course
Try a Scenario Task
Support & Messages
Uptime Data

UeduGPTs

--

Jupyters

4

Local AI

--

Uedu Code

--

Taipei AQI 57 · Taichung AQI 74 · Tainan AQI 63 · Kaohsiung AQI 64
AI Reply Desktop Notifications

Show a desktop notification when the AI TA finishes replying

Chat Message Notifications

Notify me when classmates post messages in the forum

Sound notification

Play an alert sound whenever there is a new notification

Unit 29

模組與建置工具

ES 模組、npm、Babel 與打包工具
導覽 20 minute(s)

Learning Objectives

  • 說明模組化的好處,用 ES 模組的 export(具名、預設)與 import 拆分程式
  • 用 npm init 建立專案,看懂 package.json、node_modules 與 dependencies/devDependencies
  • 說明 Babel 的用途,並完成安裝、設定與轉譯
  • 說明打包工具 webpack 的 entry、output、loader,以及現在常見的 Vite 怎麼建立專案

為什麼要把程式拆成模組

程式寫到幾千行,全部放在一個檔案裡很難維護;拆成好幾個 <script> 檔案,所有最外層的變數又都在同一個全域作用域, 兩個檔案都宣告了 add 就互相覆蓋。載入順序錯了(先用到才載入)也會出錯。

模組化是把一個獨立的功能放在一個檔案裡,每個檔案就是一個模組(module):

  • 模組裡宣告的變數、函式只在這個模組裡看得到,不會汙染全域作用域
  • 要給別人用的東西明確地 export(匯出),要用別人的東西明確地 import(匯入),依賴關係一目了然
  • 程式碼容易重複使用,也容易分工

ES2015 為 JavaScript 加入了官方的模組語法,稱為 ES 模組(ES Modules,ESM)。這一課先學語法,再介紹寫模組化專案時常用的工具:npm、Babel、webpack 與 Vite。

本課的逐步執行動畫不支援 import/export(它跑的是一般 script),所以模組的程式碼用文字說明,工具的操作用終端機導覽。

export:具名匯出與預設匯出

具名匯出(named export):在宣告前面加 export,一個模組可以有很多個。變數、常數、函式、類別都可以匯出。

// math.js
export const PI = 3.14159;
export function add(a, b) {
  return a + b;
}
function secret() { … }     // 沒有 export:只有 math.js 自己能用

也可以寫在檔案最後一次匯出:export { add, PI };。

預設匯出(default export):加上 default,一個模組只能有一個,通常用在「這個檔案主要就是提供這一個東西」時。

// print.js
export default function print(msg) {
  console.log('[訊息] ' + msg);
}

import:匯入模組

// index.js
import print from './print.js';              // 預設匯出:名稱自己取,不用大括號
import { add, PI } from './math.js';         // 具名匯出:放在大括號裡,名稱要和匯出時相同
import { add as plus } from './math.js';     // 用 as 重新命名,避免撞名
import * as math from './math.js';           // 把所有具名匯出收進一個物件:math.add、math.PI
import show, { add as sum } from './print-and-math.js';   // 同時匯入時,預設匯出寫在前面

print(add(1, 2));   // [訊息] 3

在瀏覽器裡使用模組:

<script type="module" src="index.js"></script>

ES 模組的規則:

規則 說明
自動是嚴格模式 不寫 'use strict' 也一樣,例如不能使用未宣告的變數
有自己的作用域 模組最外層的 let、const、函式不會變成全域變數,也不會掛到 window 上
只執行一次 同一個模組被好幾個檔案 import,也只會載入、執行一次,大家拿到的是同一份
路徑要寫清楚 瀏覽器裡要寫 ./math.js(相對路徑與副檔名);打包工具通常允許省略副檔名
可以用 top-level await 模組的最外層可以直接寫 await(一般 script 不行)
要用伺服器開啟 用 file:// 開啟會被 CORS 規則擋下,要用第 23 課的本機伺服器

想一想:ES 模組

1.

math.js 裡寫了 export function add(a, b) { … } 與 export default function print(msg) { … }。在 index.js 裡同時匯入兩者,下列哪一個寫法正確?

2.

一個模組可以有幾個預設匯出(export default)?

3.

util.js 是用 <script type="module"> 載入的模組,它最外層寫了 let count = 0;。另一個一般的 <script> 能用 count 或 window.count 讀到它嗎?

4.

直接在檔案總管點開一個使用 <script type="module"> 的 HTML(file:// 開頭),模組沒有執行。最可能的原因是什麼?

第 1 次作答

npm 專案:package.json 與 node_modules

第 23 課用過 npm 執行 http-server。寫一個專案時,npm 還負責記錄「這個專案用了哪些套件」:

檔案或資料夾 是什麼
package.json 專案的設定檔:名稱、版本、可執行的指令(scripts)、用到的套件
node_modules/ npm install 下載的套件實際放在這裡,通常很大,不放進 Git(寫在 .gitignore)
package-lock.json 記錄每個套件安裝的確切版本,讓每個人裝出來的都一樣,要放進 Git

package.json 裡有兩種套件清單:

清單 安裝指令 用途
dependencies npm install 套件 程式執行時需要的套件
devDependencies npm install --save-dev 套件(或 -D) 只有開發時需要的工具,例如 Babel、webpack、Vite

拿到別人的專案(沒有 node_modules)時,在專案資料夾執行一次 npm install,npm 就會依照 package.json 與 package-lock.json 把套件全部裝回來。 投影片提到的「全域安裝」(-g)是把套件裝在系統上;專案用的工具建議裝在專案裡(局部安裝),每個專案各自管理版本。

用 npm init 建立專案

先確認已經安裝 Node.js(第 23 課),打開終端機跟著做。版本不同時,輸出的欄位與數字會略有差異。

  1. 1

    建立資料夾並初始化

    npm init 會一題一題問專案名稱、版本、作者等資訊;加上 -y(--yes)就全部套用預設值,直接產生 package.json。較新版本的 npm 預設不填 license,並加上 "type": "commonjs"。

  2. 2

    設定 npm init 的預設值(可選)

    每次都要填的作者資訊,可以設成預設值,之後 npm init -y 就會自動帶入。npm config ls -l 列出所有設定。

  3. 3

    在 scripts 加上自訂指令

    用 VS Code 打開 package.json,在 scripts 裡加上指令,之後用 npm run 名稱 執行。npm start 與 npm test 是少數不用寫 run 的特例。後面 Babel、webpack 都會用到這個方法。

    "scripts": {
      "hello": "echo 你好"
    }
    
這段導覽完成了

Babel:把新語法轉成舊瀏覽器看得懂的程式

JavaScript 的標準叫 ECMAScript,2015 年的大改版(ES2015,也叫 ES6)加入了 let、const、箭頭函式、class、模組、Promise 等, 之後每年都有新版(ES2016、ES2017……)。

Babel 是 JavaScript 的轉譯器(transpiler):把新版的語法改寫成效果相同的舊語法,讓不支援新語法的瀏覽器也能執行。例如:

// 轉譯前
const fn = () => 1;

// 轉譯後(Babel 官方文件的範例)
var fn = function fn() {
  return 1;
};

投影片製作時,很多使用者還在用只支援 ES5 的舊瀏覽器(例如 IE),Babel 幾乎是必備工具。現在主流瀏覽器都已支援 ES2015 以後的大部分語法, Babel 主要用在:要支援較舊的瀏覽器、想用瀏覽器還沒全面支援的最新語法,或是轉譯 JSX(React 的語法)等非標準語法。

  • @babel/core:Babel 的核心
  • @babel/cli:在終端機執行 babel 指令
  • @babel/preset-env:依「目標瀏覽器」自動決定要轉譯哪些語法

投影片裡的 @babel/polyfill 從 Babel 7.4.0 起已經停止使用,官方建議改為直接引入 core-js/stable。 語法可以轉譯,但舊瀏覽器缺少的內建函式(例如 Array.prototype.includes)要靠這類 polyfill 補上。

安裝與設定 Babel

接著在剛剛建立的 demo 專案裡操作。

  1. 1

    安裝 Babel(開發時才需要,用 --save-dev)

    三個套件會出現在 package.json 的 devDependencies,實際檔案放在 node_modules 裡。輸出的套件數量依版本而不同。

  2. 2

    建立設定檔 babel.config.json

    在專案根目錄新增 babel.config.json,告訴 Babel 使用 preset-env,並設定要支援的瀏覽器版本:

    {
      "presets": [
        ["@babel/preset-env", {
          "targets": { "chrome": "67", "firefox": "60", "safari": "11.1" }
        }]
      ]
    }
    

    不寫 targets 時,preset-env 會把 ES2015 以後的語法全部轉成 ES5。

  3. 3

    寫程式並加上 build 指令

    新增 src/app.js,寫一些新語法,例如 const fn = () => 1;。再到 package.json 的 scripts 加上:

    "build": "babel src --out-dir dist"
    

    意思是把 src 資料夾裡的程式轉譯後放到 dist 資料夾。

  4. 4

    看轉譯的結果

    打開 dist/app.js,箭頭函式與 const 都被改寫了(Windows 的命令提示字元用 type dist\app.js)。檔案開頭可能還會多一行 "use strict";。

這段導覽完成了

打包工具:webpack

模組拆得越細,檔案就越多。早期瀏覽器不支援模組,或是一個網頁要下載上百個小檔案很慢,所以有了打包工具(bundler): 從一個入口檔案開始,順著 import 找出所有用到的模組,合併、壓縮成少數幾個檔案。

webpack 是最具代表性的打包工具,目前的主要版本是 webpack 5。它的設定檔 webpack.config.js 有幾個核心概念:

概念 意思
entry(入口) 從哪個檔案開始找依賴,預設 ./src/index.js
output(輸出) 打包結果放在哪裡、叫什麼名字,預設 ./dist/main.js
loader(載入器) webpack 本身只看得懂 JavaScript 與 JSON;其他類型的檔案(CSS、需要 Babel 轉譯的 JS)要靠 loader 轉換成模組
mode(模式) development 方便除錯,production 會壓縮檔案
// webpack.config.js
const path = require('path');

module.exports = {
  mode: 'development',
  entry: './src/index.js',
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist'),
  },
  module: {
    rules: [
      { test: /\.js$/, exclude: /node_modules/, use: 'babel-loader' },   // JS 先交給 Babel
      { test: /\.css$/, use: ['style-loader', 'css-loader'] },          // 可以 import './style.css'
      { test: /\.png$/, type: 'asset/resource' },                        // 圖片:webpack 5 內建
    ],
  },
};

每一條 rules 是「符合 test 的檔案,交給哪些 loader 處理」。use 是陣列時,從右到左執行:先 css-loader 讀進 CSS,再由 style-loader 放進頁面。 投影片的圖片用 file-loader,webpack 5 起改用內建的 asset modules(type: 'asset/resource'),不必另外安裝。

用 webpack 打包模組

開一個新的資料夾 webpack-demo,照投影片的「功能模組化範例」:src/math.js 匯出 add、src/print.js 預設匯出 print、src/index.js 匯入兩者並執行 print(add(1, 2))。

  1. 1

    建立專案並安裝 webpack

    webpack 是核心,webpack-cli 讓你在終端機執行 webpack 指令。webpack 官方建議在 package.json 加上 "private": true 並移除 "main",避免不小心把專案發布到 npm。

  2. 2

    建立設定檔與 start 指令

    在專案根目錄新增上面那份 webpack.config.js(只用到 JavaScript 時,可以先拿掉 module 那一段)。在 package.json 的 scripts 加上 "start": "webpack --config webpack.config.js"。沒有設定檔時,webpack 也會用預設值:入口 src/index.js、輸出 dist/main.js。

  3. 3

    執行打包

    npm start 執行 webpack:它從 src/index.js 找到 math.js 與 print.js,合併成 dist/main.js。最後一行看到 compiled successfully 就是成功了。

  4. 4

    在網頁裡使用打包結果

    新增 dist/index.html,用一般的 <script src="main.js"></script> 載入打包好的檔案(不需要 type="module",模組已經合併好了),用本機伺服器開啟,主控台會印出 [訊息] 3。

  5. 5

    加上 Babel loader

    要讓 webpack 打包時順便用 Babel 轉譯,安裝 babel-loader,再把上面設定檔 module.rules 的第一條加進去(Babel 的設定沿用 babel.config.json)。

這段導覽完成了

現在常見的選擇:Vite

webpack 功能完整,但設定檔要自己寫,專案大了啟動也比較慢。現在新專案常用的是 Vite(法文的「快」,念作 /vit/):

  • 開發時不先打包:直接利用瀏覽器原生的 ES 模組,存檔後畫面幾乎立刻更新
  • 幾乎不用設定:內建開發伺服器、CSS 與圖片的處理,一行指令建立專案
  • 正式發布時才打包:npm run build 產生壓縮好的檔案(Vite 8 使用 Rolldown 打包)

2026 年 10 月查詢時,Vite 的最新主要版本是 8,需要 Node.js 20.19 以上或 22.12 以上。

webpack Vite
開發時 先打包整個專案,再啟動 不打包,瀏覽器需要哪個模組才處理哪個
設定 通常要寫 webpack.config.js 大多不用設定
常見於 已經存在多年的專案 新建立的專案,也有 React、Vue 等框架的範本

學 webpack 的觀念(入口、依賴、loader、打包)仍然有用:Vite 與其他工具做的是同一件事,只是做法更自動。

用 Vite 建立專案

在任意資料夾開啟終端機,用 Vite 的官方範本建立一個純 JavaScript(vanilla)專案。

  1. 1

    建立專案

    npm create vite@latest 會下載最新版的建立工具;--template vanilla 指定不使用框架的範本。注意 --template 前面要多一組 --,讓參數傳給 Vite 而不是 npm。沒有指定範本時,它會用選單問你要用哪一種。

  2. 2

    安裝套件並啟動開發伺服器

    npm run dev 啟動 Vite 的開發伺服器,預設網址是 http://localhost:5173/。打開專案裡的 main.js 修改並存檔,瀏覽器會立刻更新。按 Ctrl+C 停止。

  3. 3

    打包成正式版本

    npm run build 把專案打包到 dist 資料夾,裡面的檔案可以直接放到任何網頁伺服器上。範本裡的 main.js 已經使用 import,你可以照本課的寫法新增自己的模組。

這段導覽完成了

想一想:npm、Babel 與打包工具

1.

從 GitHub 下載了一個前端專案,裡面有 package.json 但沒有 node_modules 資料夾。第一步要做什麼?

2.

Babel 的主要用途是什麼?

3.

webpack 的 loader 是做什麼的?

4.

下列哪一個套件適合用 npm install --save-dev 安裝(放在 devDependencies)?

第 1 次作答

重點精華回顧

  • 模組化:一個檔案一個模組,有自己的作用域,用 export 匯出、import 匯入,不汙染全域。
  • 具名匯出可以有很多個,匯入時放大括號、名稱要相同(可用 as 改名);預設匯出最多一個,匯入時自己取名、寫在前面。
  • 瀏覽器用 <script type="module"> 載入模組,要透過伺服器開啟;模組自動是嚴格模式,只執行一次。
  • npm init -y 建立 package.json;npm install 把套件裝進 node_modules;開發工具用 --save-dev 放進 devDependencies;npm run 指令 執行 scripts。
  • Babel 把新語法轉譯成等效的舊語法,搭配 @babel/preset-env 與 babel.config.json;@babel/polyfill 已停止使用。
  • webpack 從 entry 開始找出所有依賴,打包到 output;loader 處理 JavaScript 以外的檔案。
  • Vite 開發時直接使用瀏覽器的 ES 模組、幾乎不用設定,npm create vite@latest 建立專案、npm run dev 啟動。
這一課完成了!
看我的學習進度
你目前沒有登入,學習進度只存在這個瀏覽器。 Login 後,進度會跟著帳號,換電腦也看得到。