React 開發環境怎麼來的?Webpack 到 CRA 的歷史演進
更新日期: 2025 年 4 月 10 日
《React 開發環境演進史:從 Webpack 到 Vite》:
前言:React 不只是丟個 <script>
就能用?
很多剛開始學 React 的人,在 YouTube 或 Blog 上第一眼看到的教學,大多長這樣:
<!-- 在 HTML 加入 React 的 CDN -->
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
接著,在網頁中放一個空的 div
容器,然後寫幾行 JavaScript,就能跑出一個 React 元件:
<div id="root"></div>
<script type="text/javascript">
const App = React.createElement('h1', null, 'Hello React!');
ReactDOM.createRoot(document.getElementById('root')).render(App);
</script>
你可能會想:
「欸?我畫面上跑出 ‘Hello React!’ 了耶,那我是不是已經學會 React 了?」
嗯……不完全是。
這種「用 CDN 加 <script>
跑 React」的方式的確能讓你初步理解 React 的運作原理,比如:
- React 其實就是用 JavaScript 去產生 UI(
React.createElement
) ReactDOM.createRoot().render()
可以把 React 元件渲染到畫面上
對於理解基礎架構來說,這個方式非常適合入門教學用。但它跟實際開發專案時的 React 用法差非常多!
實務上沒人這樣開發 React
雖然你可以透過 <script>
和 CDN 讓 React 跑起來,但說實話,這種方式在實務上幾乎沒人在用。
你不會看到工程師每天都在寫:
React.createElement('div', null, 'Hello React');
這種原始、冗長的寫法。
也不會有人把整個網站的程式碼都塞進一個 HTML 檔案裡,用 <script>
一口氣跑完全部邏輯。
這樣寫雖然簡單,但在專案變大時,會變成一場維護的災難。
現代 React 專案都怎麼寫?
真正實務上的 React 專案,基本上都會具備以下幾個關鍵特性與工具:
功能 | 說明 | 負責的工具 / 機制 |
---|---|---|
✅ JSX 語法支援 | <h1>Hello</h1> 轉為 React.createElement() | 🔧 Babel(轉譯器) |
✅ 模組化開發(import/export) | 支援模組拆分與整合 | 🔧 打包工具(如 Webpack、Vite、Rollup) |
✅ 自動編譯(Babel / TS / JSX) | 即時轉譯 modern JS / TS / JSX | 🔧 Babel、esbuild、SWC(由 Dev Server 呼叫) |
✅ 開發伺服器(Dev Server) | 本地伺服器、即時預覽、自動刷新、Proxy | 🔥 Vite Dev Server、Webpack Dev Server |
✅ 打包工具 | 將模組、資源整合成可部署檔案 | 🧱 Webpack、Rollup、Vite(build 階段) |
✅ 靜態資源整合(CSS / 圖片 / JSON) | 允許在 JS 中 import 非 JS 資源 | 🧩 由打包器 + loader/plugin 處理 |
✅ 環境變數 / 模式切換 | 使用 .env 文件切換設定(dev/prod) | ⚙️ 打包器或框架工具處理(Vite、CRA) |
簡單來說:React 是 UI 函式庫,其他這些功能都要靠額外工具幫忙才有。
舉個實際例子:你能不能跑 JSX?
假設你今天想寫一個最簡單的 React 元件:
function Welcome(props) {
return <h1>Hello, {props.name}!</h1>;
}
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Welcome name="Coder" />);
這段程式碼裡面有 JSX(<Welcome />
),也有 ES6 語法(箭頭函式、模組 import 等)。
但你如果用 <script>
+ CDN 直接跑,瀏覽器馬上就會報錯:
Uncaught SyntaxError: Unexpected token '<'
因為瀏覽器根本不懂 <Welcome />
是什麼,它不認得 JSX,也不會幫你自動轉換。
你需要 Babel 幫你把這段「人類看得懂的程式碼」,轉成瀏覽器能跑的版本:
// Babel 轉譯後的樣子
function Welcome(props) {
return React.createElement('h1', null, `Hello, ${props.name}!`);
}
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(React.createElement(Welcome, { name: 'Coder' }));
是不是突然覺得 createElement
很難讀?
所以我們平常根本不會這樣寫,而是仰賴 JSX + Babel 的幫忙。
所以,CDN 引入只能算是「React 的 Hello World」
透過 <script>
引入 React 的方式,只是讓你快速試跑、理解概念用的。
但當你真正開始開發一個像樣的網站時,你會遇到很多需要解決的問題:
- 你的畫面拆成了 20 個元件,要怎麼管理?
- 你要載入圖片、樣式檔,要怎麼整合?
- 你想用 TypeScript,要怎麼設定?
- 你希望修改後畫面自動刷新,不想重整頁面?
- 你要部署上線,要怎麼打包、壓縮、優化效能?
這些,CDN 給不了你。
這時候,你就會需要:
Babel
幫你轉譯程式碼Webpack
或Vite
幫你打包專案開發伺服器(Dev Server)
幫你即時預覽變更環境變數、熱更新、模組系統
… 等等
最早的做法:Webpack + Babel 手動搭建 React 環境
在 React 還沒有什麼「一鍵建立專案」的時代,開發者必須自己動手一層一層地把環境搭起來,從零開始建立開發與打包流程。
而這一切的基礎,就是依賴兩個工具:Babel 和 Webpack。
Babel 是什麼?
React 最有名的特色之一,就是可以寫「像 HTML 一樣的語法」——也就是 JSX:
const element = <h1>Hello, world!</h1>;
這樣的寫法讓程式碼非常直觀、易讀,但問題是:瀏覽器根本不懂這種語法。它不認得 <h1>
出現在 JavaScript 裡面該怎麼解讀。
所以我們需要 Babel 這個「轉譯器」,它會幫我們把 JSX 轉成瀏覽器能執行的 JavaScript:
const element = React.createElement('h1', null, 'Hello, world!');
這就是 JSX 背後的真相:它其實只是語法糖,最終都會轉成 React.createElement(...)
這種寫法。
除了 JSX,Babel 還能處理各種現代 JavaScript 語法(像 async/await
、class fields、optional chaining 等),讓你可以寫出最新語法、但仍相容舊版瀏覽器。
Webpack 是什麼?
Webpack 是一個模組打包工具(module bundler)。
在原生 JS 中,瀏覽器只能吃單一個 .js
檔案,但當你的程式愈來愈大時,你會想把程式碼分檔(例如把按鈕元件寫在 Button.jsx
,表單寫在 Form.jsx
),透過 import
/ export
模組化管理。
📂 原生 JS 模組的路徑引用限制
在大型專案中,原生 JS 模組的路徑機制非常不友善,而打包工具(Webpack、Vite)是改善了這些問題。
🔸 問題 1:必須寫出副檔名
原生 JS 模組系統是以「URL」邏輯來解析路徑的,所以:
// 這樣會報錯 ❌(瀏覽器會去找一個沒有副檔名的資源)
import { Button } from './components/Button';
瀏覽器會直接向伺服器請求 /components/Button
,但這個檔案不存在(實際檔案是 Button.js
或 Button.jsx
)。
你必須寫成:
// 這樣才會成功載入 ✅
import { Button } from './components/Button.js';
而在 React 專案中,我們常常會有 .jsx
、.ts
、.tsx
,或乾脆用 Babel、TS 等編譯器讓你不用寫副檔名。
如果你用原生模組,就得每一行都寫完整副檔名,且不小心打錯副檔名就直接報錯。
🔸 問題 2:只能用相對路徑,不能用路徑別名
你一定看過這種可讀性超差的引用方式:
import Card from '../../../components/ui/Card';
當你的元件放在 src/pages/products/detail/DetailPage.jsx
,想引入 src/components/ui/Card.jsx
,你就要一直寫一堆 ../..
來回到上層,路徑又臭又長,而且只要移動檔案,這行就炸了。
但有了打包工具,你就可以在 vite.config.js
或 webpack.config.js
設定別名,像這樣:
// 設定 @ 為 src 資料夾
import Card from '@/components/ui/Card';
這樣不管你在哪個檔案,都能一致地引入元件,路徑清楚、移動檔案也不會壞掉。
🔸 問題 3:瀏覽器不支援絕對路徑
打包工具會幫你支援這樣的語法:
import Button from '/src/components/Button';
但原生瀏覽器看到 /src/components/Button.js
,會把它當成「根目錄」下的實體路徑:
http://localhost:3000/src/components/Button.js
如果你沒有正確設定伺服器(或你的檔案不是放在根目錄),就會出現 404 錯誤。
換句話說:在原生模組中,路徑真的就是「網址」,錯一點都不行。
而像 Vite、Webpack 這種工具會在你 import
的時候自動幫你虛擬處理路徑、補齊副檔名、重寫實際位置,讓你根本不用管這些繁瑣細節。
✅ 打包工具(Vite / Webpack)怎麼幫你解決?
問題 | 原生模組 | 有打包工具的環境 |
---|---|---|
副檔名一定要寫? | ✅ 要完整寫 .js / .jsx / .ts | ❌ 可自動解析、補副檔名 |
可以使用路徑別名? | ❌ 不支援 | ✅ 支援(如 @/components/Button) |
可以用絕對路徑? | ❌ 限於伺服器設定,易出錯 | ✅ 虛擬處理,自動映射至專案目錄 |
改資料夾時路徑會壞? | ✅ 非常容易壞,要手動修 | ❌ 很穩定,別名不怕搬動檔案 |
🎯 小結
「你可以用原生模組拆檔案,但你會被路徑搞瘋。」
現代開發工具的價值就是讓你不用煩路徑、副檔名、相對深度與搬檔案的風險,這些東西本來不該是開發者的煩惱。
這時候,Webpack 就會幫你把這些散落的檔案全部打包成一個瀏覽器可以執行的單一檔案(通常叫 bundle.js
),讓整個網站順利運作。
📦 打包是什麼意思
在 React 或現代前端專案中,程式碼通常會被拆分成很多個小檔案(也就是模組),例如:
src/
├── App.jsx
├── components/
│ ├── Header.jsx
│ └── Button.jsx
├── utils/
│ └── formatDate.js
每個檔案可能都會彼此 import
/ export
,形成一個龐大的依賴網狀結構。這種模組化開發方式很方便管理、維護性高,但有個問題:
❗ 瀏覽器沒辦法自動幫你組裝這些檔案,它只能載入一個個具體的 JavaScript 檔案,無法理解「模組依賴關係」。
🧱 Webpack 在這裡扮演的角色是什麼?
Webpack 就像是一台「打包工廠機器」,會從你的「入口檔案」(通常是 src/index.js
)開始,分析所有 import
的模組,接著一層一層地去解析:
- 這支檔案 import 了哪些?
- 被 import 的檔案又 import 了誰?
- 有哪些是 JavaScript、有沒有 CSS、圖片、JSON?
然後把這整個模組樹「壓縮整合」成一個可以被瀏覽器正確執行的 JavaScript 檔案,通常叫做 bundle.js
。
🛠️ 打包流程簡化圖:
這個 bundle.js
裡面包含了所有你需要的模組、樣式、甚至圖片的資訊,瀏覽器只要載入它,就能執行整個 React 應用程式。
✅ 為什麼要「打包成一支檔案」?
好處 | 說明 |
---|---|
✅ 降低 HTTP 請求數量 | 如果你有 100 個 JS 模組,瀏覽器若每個都要請求一次,載入時間會爆掉;打包成一支就一次請求搞定 |
✅ 統一壓縮與優化 | Webpack 會幫你壓縮 JS(刪除空白、註解、未使用的程式碼)加快載入 |
✅ 整合非 JS 資源 | 像 CSS、圖片、JSON 都能整合進去,變成「一個完整應用程式」 |
✅ 版本控制容易 | bundle.[hash].js 可以對抗瀏覽器快取問題,確保上線後能正確更新 |
✅ 讓瀏覽器能順利執行現代語法 | Webpack 可以結合 Babel,把 JSX / TS / ES6 全部轉成瀏覽器看得懂的標準 JS |
它還能處理更多事情:
- 壓縮 JS / CSS(節省載入時間)
- 自動加上 hash(解決快取問題)
- 管理圖片、字體、CSS 等靜態資源
但……它的設定很複雜,新手常常會卡在半路 😩
手動搭建流程超麻煩:設定地獄新手踩坑地圖
來看看最基本的 Webpack + Babel 開發環境,你得做哪些事:
第 1 步:初始化專案 + 安裝套件
npm init -y
npm install react react-dom
npm install --save-dev webpack webpack-cli babel-loader @babel/core @babel/preset-env @babel/preset-react
這邊要分清楚:
react
、react-dom
是你要實際用的函式庫- 其他一堆
--save-dev
的工具,是為了「開發過程」需要用的建構工具
新手常常不知道要分這兩類,一裝錯就整個專案壞掉。
第 2 步:設定 Babel(加 .babelrc
)
這個設定檔告訴 Babel:「我要轉 JSX,也要支援 ES6 語法」
{
"presets": ["@babel/preset-env", "@babel/preset-react"]
}
如果你少裝一個 preset,或拼錯 preset 名稱,JSX 轉不出來,React 元件一寫就爆錯 😵
第 3 步:設定 Webpack(webpack.config.js)
這個檔案是 Webpack 的設定核心,告訴它從哪裡開始打包、轉檔規則是什麼、輸出去哪裡:
const path = require('path');
module.exports = {
entry: './src/index.js', // 專案入口
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js', // 打包輸出
},
module: {
rules: [
{
test: /\.(js|jsx)$/, // 要轉換的檔案類型
exclude: /node_modules/, // 排除 node_modules
use: 'babel-loader', // 用 babel-loader 處理
},
],
},
};
這份設定對新手來說超級陌生:
- 路徑要自己打
- loader 要自己裝
- 檔案類型過濾器用正則(
test: /\.(js|jsx)$/
),完全看不懂…
第 4 步:撰寫 HTML 與手動引入打包檔案
你的 HTML 檔案也要自己寫,並手動接入 Webpack 打出來的 bundle.js
:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>My React App</title>
</head>
<body>
<div id="root"></div>
<script src="dist/bundle.js"></script>
</body>
</html>
這一切通通要手動完成,而且出錯了也沒錯誤提示,只會看到空白頁 + console 報錯,很容易讓新手懷疑人生。
新手容易崩潰的地方(踩坑地圖 🗺)
地雷點 | 為什麼會出錯 |
---|---|
.babelrc 設錯 | JSX 沒轉成功,直接報錯 |
babel-loader 沒裝或版本不對 | 無法跑 JSX,Webpack 無法編譯 |
webpack.config.js 的路徑錯 | 找不到檔案、打包失敗 |
忘記引入 bundle.js | 畫面完全不顯示 |
打包速度超慢 | 修改後還要重新 build,超浪費時間 |
報錯資訊不清楚 | 一大串錯誤 log,新手看不懂在報什麼 |
你只想印個 <h1>Hello React</h1>
到畫面上,卻要:
- 裝十幾個 npm 套件
- 設定三四個檔案
- 研究一堆 Webpack plugin
這時候,很多人直接放棄了 🙃。
這時候你只是在畫面上印一句「Hello React」,卻搞了半天還沒跑出來。
你可能花了半小時在安裝東西,一小時在 Debug 設定檔,又花了一個下午查 StackOverflow。
🤯 對新手來說,這根本不是學 React,而是被建構工具折磨
很多人會在這個階段懷疑人生:
「我不是來學 React 嗎?為什麼搞一個畫面要學 webpack、babel、plugin、一堆工具設定?」
這個時候,有些人直接放棄了,或開始到處找「有沒有不用設定的方式」。
結語:所以才有 CRA(Create React App)
後來 Facebook 推出了 Create React App,幫大家打包好這一切,只要:
npx create-react-app my-app
就能開啟一個能跑的 React 專案,不用設定 Webpack、不用摸 Babel,所有事情都藏在背後。
這樣是不是很棒?不過 CRA 雖然救了大家,也有它的問題。下一篇我們就來聊聊它的優點與限制!
重點複習
- React 雖然能用 CDN 引入,但實戰要搭建完整開發環境
- Babel 負責轉 JSX,Webpack 負責打包
- 手動設定容易出錯、上手困難
- CRA 出現讓初學者可以跳過繁瑣的設定