實戰一
準備本篇的環境
雖然可以僅展示核心程式碼,但筆者認為在一個完整的環境中邊看邊做,舉一反三,效果更佳。
這裡的環境其實就是初步認識 webpack一文完整的示例,包含 webpack、devServer、處理css、生成 html。
專案結構如下:
webpack-example2
- src // 專案原始碼
- a.css
- b.js
- c.js
- index.html // 頁面模板
- index.js // 入口
- package.json // 存放了專案依賴的包
- webpack.config.js // webpack配置檔案
src中的程式碼如下:
// a.css
body{color:blue;}
// b.js
import './c.js'
console.log('moduleB')
console.log('b2')
// c.js
console.log('moduleC')
// index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=`, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<p>請檢視控制檯</p>
</body>
</html>
// index.js
import './b.js'
import './a.css'
console.log('moduleA')
package.json:
{
"name": "webpack-example2",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack",
"dev": "webpack-dev-server"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"css-loader": "^5.2.4",
"html-webpack-plugin": "^4.5.2",
"style-loader": "^2.0.0",
"webpack": "^4.46.0",
"webpack-cli": "^3.3.12",
"webpack-dev-server": "^3.11.2"
}
}
webpack.config.js:
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
entry: './src/index.js',
output: {
filename: 'main.js',
path: path.resolve(__dirname, 'dist')
},
module: {
rules: [
{
test: /\.css$/i,
use: ["style-loader", "css-loader"]
},
]
},
plugins: [
new HtmlWebpackPlugin({
template: 'src/index.html'
})
],
mode: 'development',
devServer: {
open: true,
contentBase: path.join(__dirname, 'dist'),
compress: true,
port: 9000,
},
};
在 webpack-example2 目錄下執行專案:
// 安裝專案依賴的包
> npm i
// 啟動服務
> npm run dev
啟動伺服器後,瀏覽器會自動開啟頁面,如果看到藍色文字”請檢視控制檯“,說明環境已準備就緒。
打包樣式
處理 css 和 less
less 是一種 css 預處理語言,在 webpack 中要處理 less 需要使用 less-loader
,用於將 less 轉為 css。
首先安裝依賴,然後修改配置檔案:
// 安裝包。版本8安裝失敗,所以降了一個版本
> npm i -D less-loader@7
// webpack.config.js
// 增加對 less 檔案處理的loader
rules: [
// 需要保留,否則識別不了 css 檔案
{
test: /\.css$/i,
use: ["style-loader", "css-loader"]
},
// +
{
test: /\.less$/i,
loader: [
// compiles Less to CSS
"style-loader",
"css-loader",
"less-loader",
],
},
],
然後增加 a.less 檔案,在 index.js 中引入 a.less,重新啟動服務進行測試:
// src/a.less
body{
p{
color:pink;
}
}
// index.js
import './b.js'
import './a.css'
// +
import './a.less'
console.log('moduleA')
// 啟動服務
> npm run dev
在新開的頁面中,看到粉色文字”請檢視控制檯“,說明 less 處理成功。
提取 css 成單獨檔案
通過瀏覽器我們發現現在 css 是嵌在頁面內的,就像這樣:
<head>
...
<style>body{color:blue;}</style>
<style>body p {
color: pink;
}
</style>
</head>
通常我們會通過 link 來引入 css 檔案,所以接下來就將 css 取成單獨的檔案。這裡需要使用 mini-css-extract-plugin
這個包。
我們只需要安裝依賴包,修改配置檔案即可:
// 安裝依賴包
> npm i -D mini-css-extract-plugin@1
// 不在需要 style-loader,解除安裝
> npm r -D style-loader
// webpack.config.js
// +
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
...
module: {
rules: [
// 修改規則
{
test: /\.css$/i,
// 將 style-loader 改為 MiniCssExtractPlugin.loader
use: [MiniCssExtractPlugin.loader, "css-loader"],
},
{
test: /\.less$/i,
loader: [
// 將 style-loader 改為 MiniCssExtractPlugin.loader
MiniCssExtractPlugin.loader,
"css-loader",
"less-loader",
],
},
],
},
plugins: [
// +
new MiniCssExtractPlugin(),
...
],
};
啟動服務(npm run dev
),在開啟的頁面中可以看到 css 已經改為 link 的方式引入,就是這樣:
// 從 style 改為 link 方式
<link href="main.css" rel="stylesheet">
// 通過網路檢視 main.css 的內容是:
body{color:blue;}
body p {
color: pink;
}
由於我們對 css 和 less 都使用了 MiniCssExtractPlugin.loader,所以 a.css 和 a.less 都被提取到 main.css 中。
Tip:如果通過npm run build
打包,則可以看到 dist/main.css 檔案。
使用 PostCSS
PostCSS - 使用JavaScript轉換CSS的工具。
可以將 postcss 當作一個平臺,下面我們通過 postcss 做兩件事:
- 增加程式碼可讀性(或增加字首)
:fullscreen {
}
// 轉為
:-webkit-full-screen {
}
:-ms-fullscreen {
}
:fullscreen {
}
- 立即使用明天的CSS
body {
color: lch(53 105 40);
}
// 轉為
body {
color: rgb(250, 0, 4);
}
webpack 可以通過 postcss-loader
來使用 postcss。
由於 postcss 只是一個平臺,具體功能需要通過外掛來實現,這裡我們使用 postcss-preset-env
。
postcss-preset-env 可以將現代CSS轉換為大多數瀏覽器可以理解的內容,並根據目標瀏覽器或執行時環境確定所需的polyfill。而且它包含自動字首。
首先安裝相關依賴,並修改配置檔案:
> npm i -D postcss-loader@4 postcss-preset-env@6
// webpack.config.js
// +
const postcssPresetEnv = require('postcss-preset-env');
// +
const postcssLoader = {
loader: 'postcss-loader',
options: {
// postcss 只是個平臺,具體功能需要使用外掛
// Set PostCSS options and plugins
postcssOptions:{
plugins:[
// 配置外掛 postcss-preset-env
[
"postcss-preset-env",
{
// 自動字首。預設是true
// autoprefixer: true,
// 根據您所支援的瀏覽器來確定需要哪些polyfill。這裡僅做演示
browsers: 'ie >= 8, chrome > 10',
// stage 預設是 2
// stage:2
},
],
]
}
}
}
module.exports = {
module: {
rules: [
{
test: /\.css$/i,
use: [
MiniCssExtractPlugin.loader,
"css-loader",
// + 放在css-loader後面
postcssLoader
]
},
{
test: /\.less$/i,
loader: [
MiniCssExtractPlugin.loader,
"css-loader",
// +
postcssLoader,
"less-loader",
],
},
]
},
};
接著修改 a.css 和 a.less,重新啟動伺服器:
// a.css
body{
color: lch(53 105 40);
}
// a.less
body{
p{
transform: scale(1, 2);
}
}
// 啟動服務
> npm run dev
在新開的頁面中,我們看到紅色文字”請檢視控制檯“,而且文字縱向拉長了一倍。通過瀏覽器檢視 main.css 原始碼如下:
body{
color: rgb(250, 0, 4);
}
body p {
-webkit-transform: scale(1, 2);
-ms-transform: scale(1, 2);
transform: scale(1, 2);
}
至此,增加字首以及立即使用明天的CSS都已經完成。
Tip:stage(階段)可以是0(實驗)到4(穩定),預設是2,如果我們改為3或4,重新打包,lch(53 105 40);
則不會轉為 rgb(250, 0, 4)
;將plugins換成下面的寫法效果相同。
plugins:[
postcssPresetEnv({
browsers: 'ie >= 8, chrome > 10',
})
]
postcss-preset-env 支援任何標準的 browserslist 配置,可以是 .browserslistrc 檔案,package.json 中的browserslist 鍵或 browserslist 環境變數。
如果將 browsers: 'ie >= 8, chrome > 10',
註釋,browsers 將使用預設的 browserslist 查詢(即> 0.5%, last 2 versions, Firefox ESR, not dead
),重新構建,則不會新增字首。
如果不想在 browsers 中寫,在 package.json 中的 browserslist 中配置也是可以的:
// package.json
{
...
"browserslist": [
"ie >= 8",
"chrome > 10"
]
}
注:package.json 不能寫註釋,本文在 package.json 中的註釋僅作說明。
如果覺得 package.json 寫的內容太多,我們甚至可以將這部分提取到一個單獨的檔案中來寫:
// .browserslistrc
// from github browserslist
# Browsers that we support
ie >= 8
chrome > 10
最後,如果我們針對開發環境和生成環境做不同的處理,比如開發環境支援 ie8+,而生產環境支援 chrom10+,我們可以這麼寫:
// .browserslistrc
# Browsers that we support
[production]
chrome > 10
[development]
ie >= 8
然後在配置檔案中通過 process.env 來指定環境:
// +
// process.env屬性返回一個包含使用者環境的物件
process.env.NODE_ENV = 'development' // or production
Browserslist將根據BROWSERSLIST_ENV或NODE_ENV變數選擇,所以設定 process.env.BROWSERSLIST_ENV
也是可以的。
再次打包 npm run build
,則只會針對 ie,生成的 main.css 內容如下:
body{
color: rgb(250, 0, 4);
}
body p {
-ms-transform: scale(1, 2);
transform: scale(1, 2);
}
壓縮 css
如果我們需要壓縮 css 程式碼,可以使用 optimize-css-assets-webpack-plugin
,用於優化或最小化 css。
首先安裝依賴,然後修改配置:
> npm i -D optimize-css-assets-webpack-plugin@5
// webpack.config.js
// +
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
plugins: [
new HtmlWebpackPlugin({
template: 'src/index.html'
}),
new MiniCssExtractPlugin(),
// +
new OptimizeCssAssetsPlugin()
],
重新打包,原來的 main.css 則變成一行,請看:
> npm run build
// main.css(優化前)
body{
/* 註釋 */
color: rgb(250, 0, 4);
}
body p {
-ms-transform: scale(1, 2);
transform: scale(1, 2);
}
// main.css(優化後)
body{color:#fa0004}body p{-ms-transform:scaleY(2);transform:scaleY(2)}
優化後,css 變成了一行,註釋也刪除了。
打包圖片
前端資源通常有圖片,由於 webpack 只識別 javascript,所以需要 loader 來幫們識別圖片。
我們使用 url-loader
,能將圖片轉為 base64。
首先安裝依賴,並修改配置檔案:
> npm i -D url-loader@4
// webpack.config.js
module: {
rules: [
...
// +
{
test: /\.(png|jpg|gif)$/i,
use: [
{
loader: 'url-loader',
options: {
// 指定檔案的最大大小(以位元組為單位)
limit: 1024*7,
},
},
],
},
]
},
接著引入圖片,啟動服務:
// 引入圖片。src/6.68kb.png
// a.less
body{
p{
transform: scale(1, 2);
}
.m-box{display:block;width:100px;height:100px;}
.img-from-less{background:url(./6.68kb.png) no-repeat;background-size:100% 100%;}
}
// index.html
...
<body>
<p>請檢視控制檯</p>
<span class='m-box img-from-less'></span>
</body>
...
// 啟動服務
> npm run dev
Tip: 筆者的圖片大小為 6.68kb,上面的 limit 只需要大於6.68kb即可
在新開的頁面中,我們在”請檢視控制檯“文字下面看見了我們設定的圖片。通過檢查元素會發現這張圖片是 base64。
body .img-from-less{
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgA... no-repeat;
...
}
如果將 limit: 1024*7
修改為 limit: 1024*6
(也就是將 limit 設定的比圖片的 size 更小),再次執行 npm run dev
,會發現報錯了。還會提示找不到 file-loader。這是因為這張圖片(6.68kb.png)大於 1024*6,所以就不會被打包成 base64,所以需要 file-loader 來處理。
安裝依賴包 npm i -D file-loader@6
,再次啟動伺服器,頁面上又看到我們的圖片,而且這次不再是 base64,而是直接生成了一張圖片。
body .img-from-less{
background: url(26bd867dd65e26dbc77d1e151ffd36e0.png) no-repeat;
...
}
圖片除了在 css 中使用,我們也會通過 img 元素引用,於是我們在 index.html 中新增 <img class='m-box' src="./6.68kb.png" alt="">
再次啟動服務,在開啟的瀏覽器頁面中發現 img 引用的圖片沒生效,而且原始碼也沒變化。
這裡需要使用 html-loader 這個包,它能讓每個被載入的屬性(例如:<img src="image.png"
)能被引入(imported)。
安裝依賴包,修改配置:
> npm i -D html-loader@1
// webpack.config.js
module: {
rules: [
...
// +
{
test: /\.html$/i,
loader: 'html-loader',
},
]
},
再次啟動服務,就能看到兩張一樣的圖片了。img 的程式碼變為 <img class="m-box" src="26bd867dd65e26dbc77d1e151ffd36e0.png" alt="">
。
如果再次將 limit: 1024*6
修改為 limit: 1024*7
,啟動服務你會發現這兩處圖片都變為 base64。
打包 javascript
js 語法檢查
有時我們希望團隊成員寫的 javascript 程式碼風格一致。
我們可以使用 eslint,它能查詢並修復JavaScript程式碼中的問題;可以自定義 eslint,使其完全按照專案所需的方式工作。程式碼風格,筆者選用 airbnb,一個流行的 javascript 風格指南(此刻是第 6 名(topics javascript))。
在 webpack 中使用 eslint,需要使用 eslint-webpack-plugin
( eslint-loader廢棄了),而 eslint-webpack-plugin 依賴於 eslint
;
eslint-config-airbnb 預設匯出包含我們所有的ESLint規則,包括ECMAScript 6+和React,而 我們不需要使用 react,所以使用 eslint-config-airbnb-base
即可。
首先安裝依賴包,修改配置:
// 沒有引入 eslint-plugin-import
> npm i -D eslint@7 eslint-webpack-plugin@2 eslint-config-airbnb-base@14
// webpack.config.js
// +
const ESLintPlugin = require('eslint-webpack-plugin');
module.exports = {
// ...
plugins: [
new ESLintPlugin({
// 將啟用ESLint自動修復功能。此選項將更改原始檔
fix: true
})
],
// ...
};
// package.json
{
// +
"eslintConfig": {
"extends": "airbnb-base"
}
}
重新打包 npm run build
,出現了一些警告和錯誤,核心資訊如下:
WARNING in
webpack-example2\src\index.js
6:1 warning Unexpected console statement no-console
✖ 6 problems (2 errors, 4 warnings)
ERROR in
webpack-example2\src\index.js
1:8 error Unexpected use of file extension "js" for "./b.js" import/extensions
✖ 5 problems (2 errors, 3 warnings)
錯誤(import/extensions)是不希望使用 js 副檔名,將 ./b.js
改為 ./b
就好了,可參考issues:import/extensions。
警告(no-console)是因為不能出現 console.log。可以通過配置將這個告警關閉:
// package.json
{
"eslintConfig": {
"extends": "airbnb-base",
// +
"rules": {
"no-console": "off",
}
}
}
將import/extensions
修復,並將警告關閉,重新打包 npm run build
則不會出現警告和錯誤。
Tip:打包後,原始碼也會自動修復,比如 src/index.js 中的 sum() 方法,a,
後面是多個空格,打包後會合併成一個空格:
function sum(a, b) {
return a + b;
}
// 需要呼叫 sum() 方法
// 否則報錯:error 'sum' is defined but never used no-unused-vars
console.log(sum(1, 100));
// 修復後
function sum(a, b) {
return a + b;
}
如果在 js 檔案中使用 window ,再次打包會報錯,就像這樣:
// index.js
// +
setTimeout(() => {
window.location = 'https://www.baidu.com/';
}, 1000);
// 打包
> npm run build
...
error 'window' is not defined no-undef
可以在配置檔案中指定環境來解決這個問題。就像這樣:
// package.json
"eslintConfig": {
// +
"env": {
"browser": true
}
}
如果不想寫到 package.json,也可以配置到單獨的檔案(.eslintrc.js)中:
// .eslintrc.js
module.exports = {
"extends": "airbnb-base",
"rules": {
"no-console": "off"
},
"env": {
"browser": true
}
}
js 相容性處理
我們想使用 es6 來編寫程式碼,但有的瀏覽器支援的不夠全面,所以我們會將 es6 轉成 es5。
接著上面的例子進行,重寫 index.js,放入一個箭頭函式,再次打包,你會發現 webpack 不會對 es6 語法做處理,const 還是 const,而不是 var:
// src/index.js
const sum = (a, b) => (a + b);
console.log(sum(1, 10));
// sum 還是我們的箭頭函式
eval("const sum = (a, b) => (a + b);\nconsole.log(sum(1, 10));\n\n\n//# sourceURL=webpack:///./src/index.js?");
Babel 是一個 JavaScript 編譯器。通過它可以讓我們使用下一代的 JavaScript 語法程式設計。
在 webpack 中要使用 babel 就得用 label-loader
。用法(Usage)如下:
// 安裝依賴包。沒有使用 @babel/core
> npm i -D babel-loader@8 @babel/preset-env@7
// webpack.config.js
module: {
rules: [
// +
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: [
['@babel/preset-env']
]
}
}
}
]
}
重新打包,箭頭函式就變成普通函式:
eval("var sum = function sum(a, b) {\n return a + b;\n};\n\nconsole.log(sum(1, 10));\n\n//# sourceURL=webpack:///./src/index.js?");
如果我們在 js 中使用 Promise,重新打包後 Promise 還是 Promise,而且在不識別 Promise 語法的瀏覽器中(比如 ie11)執行會報錯。
// index.js
Promise.resolve('aaron').then((v) => {
console.log(v);
});
// 重新打包,Promise 還是 Promise
eval("Promise.resolve('aaron').then(function (v) {\n console.log(v);\n});\n\n//# sourceURL=webpack:///./src/index.js?");
babel 官網提到,使用@babel/polyfill,就可以使用新的內建函式(例如Promise或WeakMap),靜態方法(例如Array.from或Object.assign),例項方法(例如Array.prototype.includes)等等。所以這個 polyfill 是我們的解決方案。
但是 @babel/polyfill 廢棄了。而 @babel/polyfill 包含 regenerator runtime 和 core-js。
core-js,包括適用於2021年前ECMAScript的polyfill,而且僅載入必需的功能。
在 useBuiltIns 引數中也提到:由於在7.4.0中已棄用@ babel/polyfill,因此我們建議直接新增core-js並通過corejs選項設定版本。
於是我們知道 core-js 能解決 Promise 這類問題。
如何使用 core-js ?我們先來介紹一下外掛和預設。
babel 通過將外掛(或預設)應用於配置檔案來啟用Babel的程式碼轉換。比如外掛列表中的es2015
,這是一個集合,包含了箭頭函式(arrow-functions)、類(classes)等外掛;
而預設(presets)其實是多個外掛(plugin)的集合。比如 @babel/preset-env 這種預設則包含了 es2015、es2016、es2017等最新的外掛。
最後根據babel-preset-env中的介紹,我們將 core-js 應用上:
// 安裝依賴
> npm i -D core-js@3.11
// webpack.config.js
{
loader: 'babel-loader',
options: {
presets: [
[
'@babel/preset-env',
// +
{
// 配置處理polyfill的方式
useBuiltIns: "usage",
// 版本與我們下載的版本保持一致
corejs: { version: "3.11"},
"targets": "> 0.25%, not dead"
}
]
]
}
}
重新打包,dist/main.js 的Size變成 109 Kib,而之前還不到 4kiB。
啟動服務,在不支援 Promise 語法的瀏覽器中,比如 ie11,也能在控制檯輸入 aaron。
至此 javascript 的相容處理完畢。