Webpack 工程化
搭建一個 react 的環境,並且是基於 typescript 的項目
首先我們需要思考的一個問題是我這個項目需要哪些功能(可以先把能想到的功能配置進去,之後再想到的再配置,畢竟一開始可能想不全是正常的)
1. 能處理樣式 less/css 文件
2. 能處理圖片字體圖標 png/jpg/gif/svg/ttf/… 等
3. 能處理 react 組件代碼 jsx/tsx
4. 能把 ts 轉換成 js
5. 能把 es6 代碼 轉化爲 es5
6. 能處理代碼規範 eslint
一開始我也只想到了這些,so 我們先基於上面的 6 點來配置我們的項目
Step 1 初始化項目
新建一個文件夾 webpack-demo
,並且初始化項目
npm init -y
Step 2 新增配置文件
新建一個文件夾 build
來放我們的配置文件,在該目錄中新建一個 webpack 的配置文件,webpack.dev.js
並且我們在該文件中添加如下代碼,webpack 簡單的可以包括如下幾部分
- mode:什麼模式,開發 還是 生產 等
- entry: 文件的入口,可以直接一個 string 路徑
- module: 這裏就是用來幫我們處理各種文件的,比如 css、圖片、ts等
- plugin: 增加各種插件來讓我們的 webpack 功能更加強大
- output:打包輸出到哪裏
const path = require('path');
module.exports = {
mode: 'development',
entry: '',
module: {},
plugins: [],
output: {},
};
Step 3 新增配置入口
接下來就是把我們需要的配置往裏加
Webpack 需要一個入口,不然 webpack 也不知道從何下手啊,so 我們新建一個與 build 平級的 src 文件,並且新增文件 index.ts
(我們先新建一個 ts 文件,也可以是 js 文件,之後在換成 tsx 或者 jsx )
因此我們的配置就可以修改成如下這樣了
// ...
entry: './src/index.ts',
// ...
Step 4 配置 module
1. webpack 配置 babel
npm install @babel/polyfill core-js@2
npm install babel-loader @babel/core @babel/preset-env @babel/preset-react -D
.babelrc
{
"presets": [
[
"@babel/preset-env",
{
// 將es6的語法翻譯成es5語法
"targets": {
"chrome": "67"
},
"useBuiltIns": "usage", // 做@babel/polyfill補充時,按需補充,用到什麼才補充什麼,
"corejs": "2"
}
],
"@babel/preset-react"
],
"plugins": ["@babel/plugin-proposal-class-properties"]
}
在 webpack 中配置
{
test: /\.js$/,
exclude: /node_modules/,
use: [
{
loader: "babel-loader",
},
],
},
2. webpack 配置樣式(less、css)
npm install less less-loader css-loader style-loader -D
在 webpack 中配置
{
test: /\.less$/,
exclude: /node_modules/,
use: [
"style-loader",
{
loader: "css-loader",
options: {
importLoaders: 2,
},
},
"less-loader",
],
},
{
test: /\.css$/,
use: ["style-loader", "css-loader"],
},
3. webpack 配置圖片和字體圖標的處理
npm install file-loader url-loader -D
在 webpack 中配置
url-loader 基本上可以實現 file-loader 的功能,但是有一區別就是經過 url-laoder 打包後的 dist 文件下是不存在 image 文件的,這是因爲 url-loader 會把圖片轉換成 base64 的字符串直接放在 bundle.js 裏面
{
test: /\.(png|jpg|gif|jpeg)$/,
use: {
loader: "file-loader",
options: {
name: "[name]_[hash].[ext]",
outputPath: "images/",
limit: 200 * 1024, // 小於200kb則打包到js文件裏,大於則使用file-loader的打包方式打包到imgages裏
},
},
},
{
test: /\.(eot|woff2?|ttf|svg)$/,
use: {
loader: "url-loader",
options: {
name: "[name]_[hash:5].min.[ext]",
outputPath: "fonts/",
limit: 500,
},
},
},
4. webpack 配置 typescript
npm i typescript -g
npm i typescript ts-loader -D
初始化項目的 tsconfig 文件,然後根據自己的需要選擇相應的配置
通過下面指令生成的文件中所有屬性都有了,根據需要取消註釋就行
tsc --init
這是我的 tsconfig
{
"compileOnSave": false,
"compilerOptions": {
"jsx": "react",
"target": "es5" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */,
"module": "ESNext" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */,
"declaration": true /* Generates corresponding '.d.ts' file. */,
"declarationMap": false /* Generates a sourcemap for each corresponding '.d.ts' file. */,
"sourceMap": true /* Generates corresponding '.map' file. */,
"outDir": "./dist/js" /* Redirect output structure to the directory. */,
"strict": true /* Enable all strict type-checking options. */,
"noImplicitAny": false /* Raise error on expressions and declarations with an implied 'any' type. */,
"noUnusedLocals": true /* Report errors on unused locals. */,
"noUnusedParameters": true /* Report errors on unused parameters. */,
"moduleResolution": "node" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */,
"baseUrl": "./" /* Base directory to resolve non-absolute module names. */,
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies */
},
"include": ["src/**/*.ts", "src/**/*.tsx"],
"exclude": ["node_modules", "*.test.ts"]
}
在 webpack 中配置
// 處理 ts 文件
{
test: /\.ts$/,
exclude: /node_modules/,
loader: "ts-loader",
},
處理 tsx 文件需要特定的 babel 來進行轉化
{
test: /\.jsx|tsx?$/,
use: [
{
loader: 'babel-loader',
options: {
presets: [
'@babel/env',
'@babel/react',
'@babel/preset-typescript',
],
},
},
],
},
5. webpack 配置 eslint
npm install eslint -D
# 初始化配置 eslintrc,執行命令的時候就會讓你選擇一些配置,根據自己需要來就行
npx eslint --init
npm install eslint-loader -D
.eslintrc.js
module.exports = {
env: {
browser: true,
es2020: true,
node: true,
},
extends: ['plugin:react/recommended', 'standard'],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaFeatures: {
jsx: true,
},
ecmaVersion: 11,
sourceType: 'module',
},
plugins: ['react', '@typescript-eslint'],
rules: {
'prettier/prettier': 1,
},
};
在 webpack 中配置
// 修改 babel 的那個配置,增加一個 eslint 的 loader
use: ['babel-loader', 'eslint-loader'];
Step 5 配置 output
基本的 loader 已經配置好了,我們接下來配置文件打包輸出到哪裏
output: {
publicPath: './',
filename: 'bundle.js',
path: path.resolve(__dirname, '..', 'dist'),
}
可能對於上面的配置項有些不理解的可以去查看 webpack 官網中的說明,這裏只講配置的過程。
Step 6 測試配置
我們先來看看我們的配置大體上成功了沒有,這裏也僅僅是測一下配置是否有錯而已,像一些樣式什麼的文件並沒有測,你可以根據自己的情況來自測一下
在我們的入口文件裏隨便寫點代碼
在 package.json 文件中 配置 script
// 要想使用 webpack 這個命令需要你在項目裏安裝一下才行
"scripts": {
"build": "webpack --config ./build/webpack.dev.js"
}
然後 npm run build
執行就會在目錄中出現一個 dist 文件,裏面有一個 bundle.js 文件,這就是我們的輸出文件。自此說明我們的配置大體上沒啥問題(起碼能運行起來,哈哈哈~)
Step 7 增加插件,打包 html 頁面
首先我們在根目錄下新建一個文件夾 public,裏面新增一個 index.html 文件
<!-- -->
<div id = "root"></div>
<!-- -->
使用 html-webpack-plugin
插件幫我們把代碼注入到相應的模板中,並且打包到 dist 目錄中
增加 plugin
先要在項目裏安裝這個插件
npm i html-webpack-plugin -D
修改 webpack.dev.js 文件
const HtmlWebpackPlugin = require('html-webpack-plugin');
// ...
plugin: [
new HtmlWebpackPlugin({
template: path.join(__dirname, '..', 'public', 'index.html'),
}),
]
配置好之後我們再運行一下 npm run build
發現在 dist 目錄裏多了一個 index.html 文件,並且這個文件裏多了一句代碼
<script src='./bundle.js'></script>
該插件幫我們把打包出來的文件引用到了頁面中來,引用的路徑是我們在 output 裏配置的 publicPath ./
離我們的目標又近了一步,接下來我們來實現對於 react 組件的處理
Step 8 處理 react
首先我們得把我們之前的入口文件修改一下後綴名,改爲 tsx,並且裏面的內容改一下
import React from "react";
import ReactDOM from "react-dom";
const App: React.FC = () => {
return <div>Hello react</div>;
};
ReactDOM.render(<App />, document.getElementById("root"));
我們處理 tsx 文件的配置上面已經加了,所以這裏我們直接這麼寫就行了,再此運行 build 命令,我們可以打開 index.html 文件,發現頁面上出現了 hello react 的字樣,說明我們的項目已經能簡單的支持編譯處理 react 了
但是我們發現,我們每次都需要在 build 後去點開 index.html 文件才能訪問,太傻,太 low 了
Step 9 啓動本地服務訪問頁面
這我們需要在 webpack.dev.js 中新增配置 devServer
const path = require('path');
module.exports = {
mode: 'development',
entry: '',
devServer:{},
module: {},
plugins: [],
output: {},
};
我們要想用這東西還得需要安裝一下 webpack-dev-server
npm i webpack-dev-server -D
同時修改我們的 package.json 文件
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack --config ./build/webpack.dev.js",
"server": "webpack-dev-server --open",
"start": "npm run build && npm run server"
},
然後我們去修改我們的 webpack.dev.js 文件
devServer: {
contentBase: path.resolve(__dirname, '../dist'),
},
然後運行npm run server
結果發現居然報錯了
> [email protected] server /Users/mac/Desktop/webpack-demo
> webpack-dev-server --open
ℹ 「wds」: Project is running at http://localhost:8080/
ℹ 「wds」: webpack output is served from /
ℹ 「wds」: Content not from webpack is served from /Users/mac/Desktop/webpack-demo
✖ 「wdm」: Hash: 9741ca9c1aa6b6e1ab1d
Version: webpack 4.43.0
Time: 310ms
Built at: 2020/06/14 下午3:33:28
Asset Size Chunks Chunk Names
main.js 361 KiB main [emitted] main
Entrypoint main = main.js
[0] multi (webpack)-dev-server/client?http://localhost:8080 ./src 40 bytes {main} [built]
[./node_modules/ansi-html/index.js] 4.16 KiB {main} [built]
[./node_modules/html-entities/lib/index.js] 449 bytes {main} [built]
[./node_modules/loglevel/lib/loglevel.js] 8.41 KiB {main} [built]
[./node_modules/url/url.js] 22.8 KiB {main} [built]
[./node_modules/webpack-dev-server/client/clients/SockJSClient.js] (webpack)-dev-server/client/clients/SockJSClient.js 4.06 KiB {main} [built]
[./node_modules/webpack-dev-server/client/index.js?http://localhost:8080] (webpack)-dev-server/client?http://localhost:8080 4.29 KiB {main} [built]
[./node_modules/webpack-dev-server/client/overlay.js] (webpack)-dev-server/client/overlay.js 3.51 KiB {main} [built]
[./node_modules/webpack-dev-server/client/socket.js] (webpack)-dev-server/client/socket.js 1.53 KiB {main} [built]
[./node_modules/webpack-dev-server/client/utils/createSocketUrl.js] (webpack)-dev-server/client/utils/createSocketUrl.js 2.91 KiB {main} [built]
[./node_modules/webpack-dev-server/client/utils/log.js] (webpack)-dev-server/client/utils/log.js 964 bytes {main} [built]
[./node_modules/webpack-dev-server/client/utils/reloadApp.js] (webpack)-dev-server/client/utils/reloadApp.js 1.59 KiB {main} [built]
[./node_modules/webpack-dev-server/client/utils/sendMessage.js] (webpack)-dev-server/client/utils/sendMessage.js 402 bytes {main} [built]
[./node_modules/webpack-dev-server/node_modules/strip-ansi/index.js] (webpack)-dev-server/node_modules/strip-ansi/index.js 161 bytes {main} [built]
[./node_modules/webpack/hot sync ^\.\/log$] (webpack)/hot sync nonrecursive ^\.\/log$ 170 bytes {main} [built]
+ 17 hidden modules
ERROR in Entry module not found: Error: Can't resolve './src' in '/Users/mac/Desktop/webpack-demo'
ERROR in multi (webpack)-dev-server/client?http://localhost:8080 ./src
Module not found: Error: Can't resolve './src' in '/Users/mac/Desktop/webpack-demo'
@ multi (webpack)-dev-server/client?http://localhost:8080 ./src main[1]
ℹ 「wdm」: Failed to compile.
一開始我以爲是我的 output 中的 publicPath 這個路徑配置影響到了我的 server,因爲啓動 server 的時候顯示
「wds」: webpack output is served from /
「wds」: Content not from webpack is served from /Users/mac/Desktop/webpack-demo
這明顯不是我想要的 dist 目錄啊,所以就增加了 publicPath ,devServer 裏的 publicPath 和 output 裏的其實作用一樣,只是如果 devServer 裏如果不加就默認是 output 中的,我在 devServer 里加了
// ...
publicPath: './index.html'
// ...
結果再次運行發現還是報一樣的錯,而且路徑也沒有改變,貌似沒起作用
看了網上很多說的,有說是因爲 html-webpack-plugin 這個插件與 webpack-dev-sever 配置衝突,需要 publicPath 裏 ”dist/index.html“ 這樣寫,結果說的基本上都試了,結果都不行,後來在 stack overflow 中看了一個同樣問題的,在評論裏有人說,修改我們的 script,增加一個 —config
I also just get this error like you got and its was solved when i specify webpack config file. Try to add --config when you run webpack-dev-server. Ex. webpack-dev-server --config webpack.dev.js
於是乎,我也修改了我的 script
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack --config ./build/webpack.dev.js",
"server": "webpack-dev-server --config ./build/webpack.dev.js --open",
"start": "npm run build && npm run server"
}
然後發現可以了,具體的原因其實我也不知道爲什麼, 尷尬啊~~
自此,我們的項目基本上也可以正常的運行起來了,還不錯的樣子吼~~
總結
Webpack 的運行流程是一個串行的過程,從啓動到結束會依次執行以下流程:(網上找來的,具體哪裏忘記了,尷尬~~)
- 初始化參數:從配置文件和 Shell 語句中讀取與合併參數,得出最終的參數;
- 開始編譯:用上一步得到的參數初始化 Compiler 對象,加載所有配置的插件,執行對象的 run 方法開始執行編譯;
- 確定入口:根據配置中的 entry 找出所有的入口文件;
- 編譯模塊:從入口文件出發,調用所有配置的 Loader 對模塊進行翻譯,再找出該模塊依賴的模塊,再遞歸本步驟直到所有入口依賴的文件都經過了本步驟的處理;
- 完成模塊編譯:在經過第4步使用 Loader 翻譯完所有模塊後,得到了每個模塊被翻譯後的最終內容以及它們之間的依賴關係;
- 輸出資源:根據入口和模塊之間的依賴關係,組裝成一個個包含多個模塊的 Chunk,再把每個 Chunk 轉換成一個單獨的文件加入到輸出列表,這步是可以修改輸出內容的最後機會;
- 輸出完成:在確定好輸出內容後,根據配置確定輸出的路徑和文件名,把文件內容寫入到文件系統。
在以上過程中,Webpack 會在特定的時間點廣播出特定的事件,插件在監聽到感興趣的事件後會執行特定的邏輯,並且插件可以調用 Webpack 提供的 API 改變 Webpack 的運行結果。
總的來說,我們需要工程化一個項目,是基於我們的目的來說的,你希望這個工程具備什麼樣的功能,你就往裏加,一開始誰都無法考慮的很周全,一點點來,先把基本的架子搭出來,然後往裏填充,讓這個架子更加豐滿。
也許我上面的配置還有隱藏的一些錯誤或的地方,如果又啥問題歡迎指出來~~