有多少人從create-react-app換去使用vite了呢?在早期的教學影片當中,常常會看到講師使用create-react-app 開啟新的專案,導致後期的調整不太會使用,因為通常結尾也只是簡單的下 build 的指令而已,至於該如何調整webpack的部分,在新手階段,調整 webpack 通常都不會是我們的工作,但在走出新手村之後,這部分的概念就是必須搞懂的過程。
首先,我們必須要有基礎的理解,webpack到底幫我們處理了什麼?
webpack作為一個 JavaScript 的 Bundle 工具,用於將多個 JavaScript 檔案和相關資源打包成單一的檔案,以減少網頁載入時間並提高應用程式的效能,Bundle 就如同他翻譯意思「捆綁」一樣,將多個模組或檔案合併成一個捆綁檔案,使瀏覽器只需下載和載入單一檔案,而不是多個分散的檔案。
當然,也有其他的打包工具,例如:parcel, rollup, gulp….,但這裡主要分享業界普遍標準 webpack 要怎麼處理 React 的專案設定。
CRA(creact-react-app)的底層也用webpack作為包版工具,但他的問題如下:
那麼早期的專案是怎麼透過webpack設定的呢?
這就回歸了一個基本的概念 — 瀏覽器的支援,基本上目前都有支援到 ES6,少部分版本是 ES5+ 但會逐漸淘汰,但為了保險起見通常會透過babel轉譯,就是把你那些寫好給人看的code進行壓縮,轉成瀏覽器能運行的code。同理,html & css也能通過設定做壓縮,包含檔案存放路徑的調整,甚至能做到code splitting的功能,但這前提是能自由的操控webpack,下面我就演示一下透過webpack來做React的基本設定。
請不要忘記我們前端構成的主要三元素檔案 — Html / Css / Javascript,這個在接下來的步驟當中可以幫助你記得你在幹嘛?我們的目的是壓縮這三個元素的檔案。
// 在你專案的根目錄下
npm init --y
// or
yarn init --y
// 安裝 typescript (optional)
npm i tascript ts-loader --save-dev
// or
yarn add tascript ts-loader --dev
npx tsc --init
npm i webpack webpack-cli webpack-dev-server --save-dev
// or
yarn add webpack webpack-cli webpack-dev-server --dev
npm i babel-loader @babel/core @babel/preset-env @babel/runtime @babel/plugin-transform-runtime @babel/preset-react --save-dev
// or
yarn add babel-loader @babel/core @babel/preset-env @babel/runtime @babel/plugin-transform-runtime @babel/preset-react --dev
npm i html-webpack-plugin css-loader style-loader postcss-loader postcss postcss-preset-env --save-dev
// or
yarn add html-webpack-plugin css-loader style-loader postcss-loader postcss postcss-preset-env --dev
npm i react react-dom tailwindcss --save
// or
yarn add react react-dom tailwindcss
// 如果是使用typescript的朋友要記得多處理以下
npm i @types/react @types/react-dom --save-dev
// or
yarn add @types/react @types/react-dom --dev
webpack.config.js
檔案,這裡就是調整你的打包設定,其實每個設定都可以在webpack的官方文件中找到詳細設定方式,我這邊就不再贅述太多細節,主要還是以基礎設置為主。// webpack.config.js
const HtmlWebPackPlugin = require("html-webpack-plugin");
module.exports = {
// 這裡不想使用typescript的朋友可以考慮用回.jsx檔名
entry: "./src/index.tsx",
output: {
publicPath: "http://localhost:3000/",
},
resolve: {
extensions: [".tsx", ".ts", ".jsx", ".js", ".json"],
},
devServer: {
port: 3000,
historyApiFallback: true,
},
module: {
rules: [
{
test: /\.m?js/,
type: "javascript/auto",
resolve: {
fullySpecified: false,
},
},
{
test: /\.(css|s[ac]ss)$/i,
// 這裡不想用tailwind的朋友可以不用設定和載入postcss-loader
use: ["style-loader", "css-loader", "postcss-loader"],
},
{
test: /\.(ts|tsx|js|jsx)$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
},
},
],
},
plugins: [
new HtmlWebPackPlugin({
template: "./src/index.html",
}),
],
};
.babelrc
檔案調整載入 babel-loader 的設定{
"presets": ["@babel/preset-react", "@babel/preset-env"],
"plugins": [
["@babel/transform-runtime"]
]
}
// 先長出tailwind.config.js
npx tailwindcss init
讓我們將長出來的tailwind.config.js
檔案調整成以下:
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ["./src/**/*.{js,jsx,ts,tsx}"],
theme: {
extend: {},
},
plugins: [],
}
接著,於根目錄下新增postcss.config.js
檔案如下:
const tailwindcss = require("tailwindcss");
module.exports = {
plugins: ["postcss-preset-env", tailwindcss],
};
再來,我們調整一下tsconfig.json
檔案
{
"compilerOptions": {
"allowUnreachableCode": false,
"exactOptionalPropertyTypes": true,
"noFallthroughCasesInSwitch": true,
"noImplicitThis": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitAny": true,
"noImplicitReturns": true,
"alwaysStrict": true,
"strictBindCallApply": true,
"strictFunctionTypes": true,
"strictNullChecks": true,
"strictPropertyInitialization": true,
"jsx": "react",
"esModuleInterop": true,
"allowJs": true,
"checkJs": true,
"isolatedModules": true
},
"include": ["src/**/**/*"],
"exclude": ["node_modules", "dist"]
}
那我們按照常理先來設定index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Webpack React</title>
</head>
<body>
<div id="app"></div>
</body>
</html>
接著我們來設定index.tsx
或是index.jsx
import React from 'react';
import { createRoot } from 'react-dom/client';
import './index.scss';
import App from './App';
const rootEl = document.querySelector("#app");
if (!rootEl) throw new Error("Cannot find app element with that id");
const root = createRoot(rootEl)
// 可以使用 root 物件來渲染對應的元件,並加上嚴格模式
root.render(
<React.StrictMode>
<App />
</React.StrictMode>,
);
加上index.scss
檔案,這裡只是示範,當然你可以改用一般Css檔案,如果是使用 tailwind 如下設定:
@tailwind base;
@tailwind components;
@tailwind utilities;
最後如往常一樣設定App.tsx
或者App.jsx
import React from "react";
const App = () => {
return (
<div className="container mx-auto">
<h2 className="text-primary text-4xl font-bold">Hello webpack</h2>
</div>
)
}
export default App;
package.json
檔案的 script 如下:{
"name": "wp-react-18-ts",
"version": "1.0.0",
// 通常會放在這個位置
"scripts": {
"build": "webpack --mode production",
"start": "webpack serve --open --mode development"
},
// ...省略
}
npm run start
// or
yarn start
基本上這些設定在各大包板工具中都大同小異,但我會說 webpack 比較有歷史,不管是資源還是支援他都能勝任現在的任務,就算是其他新興的工具,也都是以他為模板目標,盡量讓大家能無痛移轉,所以能學會 webpack 可以說是很重要的技能。
當然,我相信 vite, rspack, turbopack …, 這類的 build tool 更加普及了以 後,會更少人使用 webpack,因為不管是處理速度、壓縮比例、效能…,都會有很大的提升,如果底層語言是以 Rust, go, zig…, 這些高效能的語言來處理的話,本身以 nodejs 為基礎開發的 webpack 會有很難越過的坎。
但如果你也在業界工作,你會發現很多老專案都是採用 webpack 來處理的,再加上他完整的資源與歷史累積下來的市佔,就算真的遇到 bug,要想透過chatGPT 或是 stack Overflow 找到解答,應該都不會是件難事。
那麼今天的內容就到這裡,下一篇我們來回顧 React 的發展歷史。