webpack的入門實踐
我會將所有的讀者概括為初學者,即使你可能有基礎,學習本節之前我希望你具有一定的JavaScript和node基礎
- 文中的
... ...
代表省略掉部分程式碼,和上面的程式碼相同 - 文中的資料夾如果沒有說建立,並且專案預設沒有的是需要你手動增加的
- 不會特別細緻,但是足夠入門
資源
什麼是webpack
Web瀏覽器使用HTML,CSS和JavaScript。隨著專案的發展,跟蹤和配置所有這些檔案變得非常複雜,解決這個問題就需要一個新的工具
類似webpack的工具還有Grunt和Gulp,webpack是模組管理工具,把你的專案按照你的想法進行劃分模組打包,舉個最簡單的例子,這個頁面需要載入一個 a.js
和b.js
,但是你只想載入一個js檔案,就可以使用webpack將兩個檔案進行合併,當然webpack的功能不止於此,程式碼轉化、專案優化、程式碼分割、程式碼預編譯、自動構建、自動重新整理...
再比如你想你的程式碼相容其他老的瀏覽器,你的css程式碼相容不同的瀏覽器核心,或者你想自動精簡掉你寫了但是沒有用到的程式碼,這些都可以使用webpack實現
如果你是vue或者react等框架的使用者,肯定使用過 vue-cli 或 react-create-app 這類腳手架工具,那麼實現這個效果,就要學習webpack
快速入門
注意本文都是webpack4的內容
安裝
建立一個 webpackdemo資料夾,使用命令npm init -y
快速初始化一個專案
安裝 webpack可以使用全域性安裝
npm install webpack -g
但是我更推薦你在每個專案裡面單獨引入,這樣可以控制版本,如果你使用 webpack 4+ 版本,你還需要安裝 CLI。
npm install -D webpack@<version>
npm install -D webpack-cli
本文預設使用專案引入的方式,我們在根目錄下新建 src/index.js,webpack在不進行任何配置的情況下,會預設尋找這個檔案
然後命令列執行node_modules\.bin\webpack
,如果你是全域性安裝的可以直接使用webpack
命令
注意此時命令列爆黃色警告,這是沒有指定當前模式的原因,並且可以發現,目錄下多了一個 dist/main.js
檔案,這便是預設的輸出檔案
為了體驗專案的打包,我們新建一個src/clg.js
檔案
export default function clg(msg) {
console.log(msg);
}
我們在index.js
裡面匯入並使用
import clg from './clg';
clg('webpack init');
然後根目錄我們新建一個 index.html
檔案,引入打包後的檔案
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="./dist/main.js"></script>
</head>
<body>
</body>
</html>
然後修改一下打包命令,指定當前為開發模式,再次執行.\node_modules\.bin\webpack --mode development
這次打包沒有爆警告,並且我們開啟index.html
控制檯檢視結果
webpack.config.js配置檔案
在前面,我們都是使用webpack-cli
為我們提供的預設配置,如果我們想使用webpack更強大的功能還是需要自定義配置檔案的,在根目錄新建webpack.config.js,執行webpack命令的時候會自動找到它
const path = require('path');
module.exports = {
// 環境
mode: 'development',
// 目標檔案
entry: [path.resolve(__dirname, './src/index.js')],
// 自定義輸出檔案
output: {
path: path.resolve(__dirname, './dist'), //路徑
filename: 'main.js' //檔名稱
},
// 外掛
plugins: [
],
// 給匯入的檔案制定規則
module: {
}
}
為了方便除錯,我們在 package.json
中新增命令,此時執行命令npm run dev
或npm run build
就非常方便了
注意 scripts命令裡面可省略
.\node_modules\.bin\
使用npx webpack
也是這個效果
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev":"webpack --mode development",
"build":"webpack --mode production"
},
多個目標和輸出檔案
在上面是一個目標檔案index.js
和一個輸出檔案main.js
,如果我們想要對多個目標檔案進行打包,且輸出多個檔案該怎麼辦呢?我們在根目錄新建一個 other.js
首先我們將entry修改為多個目標檔案,並設定一個鍵
名,然後修改輸出檔案的名稱為變數[name]
const path = require('path');
module.exports = {
// 環境
mode: 'development',
// 目標檔案
entry: {
index: path.resolve(__dirname, './src/index.js'),
other: path.resolve(__dirname, './src/other.js')
},
// [path.resolve(__dirname, './src/index.js'), path.resolve(__dirname, './src/other.js')],
// 自定義輸出檔案
output: {
path: path.resolve(__dirname, './dist'), //路徑
filename: '[name].bundle.js' //檔名稱
},
}
此時我們執行 npm run build
可發現dist目錄的多個js檔案
使用外掛來測試程式
在上面,我們自己建立了一個 index.html
檔案來測試我們打包的檔案是否正常,其實webpack為我們提供了更為自動的方式,在這裡我們將使用第一個webpcak外掛html-webpack-plugin
,首先需要安裝它
npm i html-webpack-plugin -D
然後我們在 webpack.config.js
中配置使用這個外掛
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
// 目標檔案
entry: {
index: path.resolve(__dirname, './src/index.js'),
other: path.resolve(__dirname, './src/other.js'),
},
// 自定義輸出檔案
output: {
path: path.resolve(__dirname, './dist'), //路徑
filename: 'main.js' //檔名稱
},
// 外掛
plugins: [
new HtmlWebpackPlugin({
title: "Webpack init",
}),
],
}
此時,我們刪除 index.html
檔案,然後再次執行npm run build
可以發現,webpack自動為我們建立了一個indedx.html
檔案,並引入了打包後的js檔案
多個頁面
在上面,我們都是使用的一個 index.html
單頁面,實際開發中漸進式的單頁面程式也比較多,但是還是會有多頁面的場景
簡單的修改一下webpack.config.js
,多次例項化外掛就可以了,filename
為輸出檔名,chunks
為這個頁面需要使用的js檔案,當然如果你不是使用的自動生成頁面,可以使用template
屬性指定你的頁面位置
module.exports = {
... ...
// 外掛
plugins: [
new HtmlWebpackPlugin({
filename:"index.html",
title: "Webpack init",
chunks:['index']
}),
new HtmlWebpackPlugin({
filename:"other.html",
title: "Webpack init",
chunks:['other']
}),
new webpack.NamedModulesPlugin(),
new webpack.HotModuleReplacementPlugin(),
new CleanWebpackPlugin(),
],
... ...
}
此時我們使用npm run dev
,此時可以發現dist
目錄輸出了兩個頁面並引入不同的js檔案
source map
打包後的js檔案都混淆到了一個或者多個檔案中,丟失了原本的檔案格式,如果在執行過程中出現bug,很難定位原本的錯誤位置 source map 就可以解決這個問題
為了更容易地追蹤錯誤和警告,JavaScript 提供了 source map 功能,將編譯後的程式碼對映回原始原始碼。如果一個錯誤來自於
b.js
,source map 就會明確的告訴你。
開啟 source map 非常簡單,只需要在配置檔案webpack.config.js
中增加
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack');
module.exports = {
... ...
devtool: "cheap-module-eval-source-map",
... ...
}
為了驗證是否生效,我們在other.js
中增加
console.log('other');
console.error("error")
然後使用npm run dev
,接著在控制檯檢視錯誤並點選,便能跳轉到出錯的位置
devtool有多個模式,不同的效能和品質,開發環境中我們希望效能更好,生產環境我們希望質量更好詳細配置
開發環境可以使用cheap-module-eval-source-map、eval 、eval-source-map
生產環境可以使用inline-source-map、inline-cheap-module-source-map、cheap-source-map
觀察模式和webpack-dev-server
在上面的內容中,每次修改內容後都需要手動執行構建命令,webpack為我們提供了更為自動的方法
觀察模式
我們只需簡單的修改一下命令npm run dev --watch
,同樣的我們為了方便,可以直接將命令寫入 package.json
中
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "webpack --mode development",
"build": "webpack --mode production",
"watch":"webpack --mode production --watch"
},
此時,我們執行npm run watch
命令後,只要修改檔案中的內容,webpack即可自動構建
webpack-dev-server
但是在實際開發中,使用webpack-dev-server(簡稱wds)更為方便,它為我們提供了一個簡單的伺服器,並且瀏覽器能夠實時載入,也就是說,當你修改檔案儲存後,瀏覽器可自動載入最新的內容,並且這一切都是發生在記憶體中,構建速度更快
安裝
npm i webpack-dev-server -D
同樣的我們在 package.json
中新增一個命令
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "webpack --mode development",
"build": "webpack --mode production",
"watch": "webpack --mode production --watch",
"server": "webpack-dev-server --mode development"
},
此時,我們只需執行npm run server
,webpack便可自動建立一個伺服器,並將專案執行在其中,當我們修改檔案中的任意內容的時候,頁面便會自動重新整理
如果使用過 vscode的外掛live server的同學,不難發現,這就是類似的功能
我們還可以在 webpack.config.js
中進一步的對 wds進行配置
module.exports = {
... ...
devServer: {
/**
* 日誌模式 friendly-errors-webpack-plugin 外掛可以優化輸出
* errors-only 只在發生錯誤時觸發
* minimal 只在發生錯誤或者有新的編譯時輸出
* none 沒有輸出
* normal 標準輸出
* verbose 全部輸出
*/
stats: "errors-only",
//預設地址 localhost
host: process.env.HOST,
//預設埠 8080
port: process.env.PORT,
//是否直接開啟瀏覽器
open: true,
},
... ...
}
此時我們再次執行 npm run server
,webpack便能按照我們的配置來構建了
HMR
模組熱替換(Hot Module Replacement 或 HMR)是 webpack 提供的最有用的功能之一。它允許在執行時更新各種模組,而無需進行完全重新整理。
在上面的內容中,我們修改檔案的部分內容,webpack都需要將專案重新構建並通知瀏覽器重新渲染,這個過程十分浪費資源,使用 HMR就可以實現,修改哪裡,重新載入哪裡的這個效果
NamedModulesPlugin外掛是在熱載入時直接返回更新檔名
使用 HMR我們只需要簡單的配置即可
webpack.config.js
... ...
const webpack = require('webpack');
module.exports = {
... ...
// 外掛
plugins: [
... ...
new webpack.NamedModulesPlugin(),
new webpack.HotModuleReplacementPlugin()
],
devServer: {
... ...
//是否開啟熱更替
hot: true
},
}
為了驗證是否是區域性更替,我麼修改一下檔案內容
index.js
import clg from './clg';
console.log('webpack init');
// module.hot Webpack通過全域性變數公開HMR介面
if (module.hot) {
module.hot.accept('./clg.js', function () {
clg('檢測到clg模組修改');
})
}
此時我們使用 npm run server
將專案執行起來,簡單的修改 index.js檔案中內容,發現控制檯只列印了
我們再次修改clg.js
中內容,打個空格再儲存即可,此時驗證了我們想要的效果
生產環境和開發環境分離
開發環境(development)和生產環境(production)的構建目標差異很大,官方建議為每個環境編寫彼此獨立的 webpack 配置。
我們將新建兩個配置檔案webpack.dev.js(開發環境)
和webpack.prod.js(生產環境)
但是它們具有很多相同的配置,所以我們再新建一個webpack.common.js(通用配置)
檔案
我們使用webpack-merge
外掛來將不同的環境配置檔案和通用配置檔案進行合併,並且使用clean-webpack-plugin
外掛來每次重置我們的構建資料夾
npm i webpack-merge -D
npm i clean-webpack-plugin -D
webpack.common.js
const path = require('path');
const webpack = require('webpack');
const {
CleanWebpackPlugin
} = require("clean-webpack-plugin");
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: [path.resolve(__dirname, './src/index.js')],
plugins: [
new CleanWebpackPlugin(),
new webpack.NamedModulesPlugin(),
new HtmlWebpackPlugin({
title: 'Webpack init'
})
],
output: {
filename: 'main.js',
path: path.resolve(__dirname, 'dist')
},
module: {
rules: [
]
}
};
webpack.dev.js
我們再開發環境配置檔案中配置 server的相關資訊,並且開啟source-map
const merge = require('webpack-merge');
const common = require('./webpack.common.js');
module.exports = merge(common, {
mode:"development",
devtool: 'eval',
devServer: {
stats: "errors-only",
//預設地址 localhost
host: process.env.HOST,
//預設埠 8080
port: process.env.PORT,
//是否直接開啟瀏覽器
open: true,
//是否開啟熱更替
hot: true,
},
module: {
rules: [
//打包css檔案的規則
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
}
]
}
});
webpack.prod.js
我們在生產環境配置檔案中省略掉其他配置檔案
const merge = require('webpack-merge');
const common = require('./webpack.common.js');
module.exports = merge(common, {
mode: "production",
});
然後我們在 package.json增加一些命令
"envdev":"webpack-dev-server --config webpack.dev.js",
"envbuild":"webpack --config webpack.prod.js"
此時,我們的專案構建就更加清晰了
管理資原始檔
webpack不僅僅是打包 js檔案這麼簡單,此處我們簡單的介紹幾個常用的資源打包方式,更詳細的內容可以參考官方文件
管理css
使用過腳手架的同學應該都記得,專案裡面的css檔案可以通過js直接引入的方式使用
import 'xxx.css';
來簡單實踐一下,首先安裝外掛
npm i style-loader css-loader -D
為了展示我們的打包效果,我們新建一個 js/divdoc.js
檔案用來在頁面中渲染出一個字串(此時我們已將clg.js也轉移到 ./js
資料夾)
export default function divdoc() {
//建立一個dom
let element = document.createElement('div');
element.innerHTML = "webpack init";
element.classList.add('init');
//將dom渲染到頁面上
document.body.appendChild(element);
}
在index.js中匯入並使用
import clg from './js/clg';
import divdoc from './js/divdoc';
console.log('webpack init now');
divdoc();
// module.hot Webpack通過全域性變數公開HMR介面
if (module.hot) {
module.hot.accept('./js/clg.js', function () {
clg('檢測到clg模組修改');
})
}
此時執行 npm run server
檢視效果
接下來我們新建 src/css/app.css
檔案
我們前面渲染的dom節點是包含一個 class名為 init的
.init{
color: red;
}
編寫規則webpack.dev.js
module.exports = {
... ...
// 給匯入的檔案制定規則
module: {
rules: [
//打包css檔案的規則
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
},
]
}
}
在index.js中匯入,並檢視效果
import './css/app.css';
順便可以打包一下less,首先安裝外掛npm i -D less-loader
,然後寫一下規則
module: {
rules: [
//打包css檔案的規則
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
},
{
test: /\.less$/,
use: ['style-loader', 'css-loader', 'less-loader']
},
]
}
我們新建一個 src/css/app.less
檔案
.init{
color: red;
}
我們在 index.js
中註釋掉原本匯入的 app.css
,然後匯入less檔案
import './css/app.less';
重新構建專案,檢視專案,效果依然生效,同理sass、stylus
也是這個用法,這裡不再贅述
分離css
在前面,我們打包css,最終都是將css程式碼新增到頁面的 style標籤中,如果我們想將所有的css都打包到專門的檔案裡面可以使用mini-css-extract-plugin外掛
npm i mini-css-extract-plugin -D
然後修改一下配置webpack.prod.js
... ...
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports = {
... ...
// 外掛
plugins: [
new MiniCssExtractPlugin({
filename: "styles/[name].css",
})
],
// 給匯入的檔案制定規則
module: {
rules: [
//打包css檔案的規則
// {
// test: /\.css$/,
// use: ['style-loader', 'css-loader']
// },
{
test: /\.css$/i,
use: [
MiniCssExtractPlugin.loader, 'css-loader'
],
}
]
}
... ...
}
此時我們執行npm run build
可以發現dist
目錄下面建立了一個index.css
檔案,因為我們是在 index.js
中匯入css檔案的,[name]
的值是js的檔名,而不是css的名
當然如果你想指定輸入和匯出的css的名字也是可以的,使用這種方式,你就不需要在js中再次引入css檔案了
entry: {
index: path.resolve(__dirname, './src/index.js'),
other: path.resolve(__dirname, './src/other.js'),
app: path.resolve(__dirname, './src/css/app.css')
},
webpack在處理樣式方面還有很多很強大的外掛,比如purgecss可以精簡掉頁面中沒有使用的css樣式、Autoprefixer可以自動給你新增不同瀏覽器相容的css外掛
管理圖片和字型
在網頁中,圖片一般都是載入的網路路徑,但是在開發中我們都是使用的本地圖片,那麼為了保證上線後和本地的資源位置保持一致,我們可以使用webpack來進行一下打包,最後統一上傳oss儲存
首先需要安裝url-loader
npm i url-loader -D
然後我們在 webpack.common.js
中進行配置
const path = require('path');
const webpack = require('webpack');
const {
CleanWebpackPlugin
} = require("clean-webpack-plugin");
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: [path.join(__dirname, './src/index.js')],
plugins: [
new CleanWebpackPlugin(),
new webpack.NamedModulesPlugin(),
new HtmlWebpackPlugin({
title: 'Webpack init'
})
],
output: {
filename: 'main.js',
path: path.resolve(__dirname, 'dist'),
//靜態檔案打包的網路路徑
publicPath:'https://www.lookroot.cn/assets/'
},
module: {
rules: [
{
test: /\.(png|svg|jpg|gif)$/,
use: {
loader: 'url-loader',
options: {
//超過這個大小,圖片就打包為圖片,不超過就打包為base64格式的程式碼
limit: 1000,
//打包檔名
name: "img/[hash].[ext]",
},
}
},
]
}
};
為了驗證,首先我們放一張圖片logo.jpg
到src/asserts/img
目錄下,我們簡單的修改一下app.css
.init {
color: red;
}
body {
background-image: url("../assets/img/logo.jpg");
background-repeat: no-repeat;
}
同樣的我們也可以在js程式碼裡使用圖片,修改一下divdoc.js
import logo from "../assets/img/logo.jpg";
export default function divdoc() {
... ...
// 插入一張圖片
let img = document.createElement('img');
img.src = logo;
element.appendChild(img);
... ...
}
然後使用npm run envdev
執行起來,效果是正常的
然後我們使用npm run envbuild
執行編譯,然後我們開啟dist/img/main.css
body {
background-image: url(https://www.lookroot.cn/assets/img/494654d849ba012e2aab0505d7c82dc0.jpg);
background-repeat: no-repeat;
}
我們可以發現,webpack就給我們自動加上了網路路徑,對於圖片的處理,還有可以優化圖片的[image-webpack-loader(https://github.com/tcoopman/image-webpack-loader)、可以自動生成雪碧圖的postcss-sprites
除了上面我說的這些資源外,webpack還支援非常多的資源格式,只要理解這個思想,使用也不難
程式碼檢查和程式碼轉換
Eslint程式碼檢查
eslint是實際開發中非常常用的程式碼檢查工具,我們在webpack中使用它來進行程式碼檢查
首先安裝eslint、loader、錯誤格式化外掛
npm i eslint eslint-loader eslint-friendly-formatter -D
然後我們在根目錄新建一個.eslintrc.json
,當然你也可以使用命令npx eslint --init
來初始化配置檔案
rules代表規則,這裡我設定一個禁止使用 alert
程式碼來測試是否可以完成程式碼檢查,更多規則請看文件
{
"env": {
"browser": true,
"es6": true,
"node": true
},
"extends": "eslint:recommended",
"parserOptions": {
"ecmaVersion": 11,
"sourceType": "module"
},
"rules": {
"no-alert": 2
}
}
然後在webpack.dev.js
中增加配置
module: {
rules: [
... ...
{
test: /\.js$/,
loader: 'eslint-loader',
enforce: 'pre',
include: [path.resolve(__dirname, 'src')],
options: {
formatter: require('eslint-friendly-formatter')
}
}
]
}
然後我們在 index.js
檔案中增加一句alert("webpack init")
,然後使用命令npm run envdev
發現報錯,eslint成功捕捉到了錯誤
同樣的你還可以使用 StyleLint工具來檢查你的css程式碼
babel程式碼轉化
在實際開發中如果使用了 es6+的程式碼,有些瀏覽器是不支援的,為了相容,所有需要將程式碼進一步轉化,可以使用babel進行轉化
babel的使用稍微比較繁瑣,本文只介紹在webpack的使用方法,更多細緻的東西請自行查閱
安裝本體和loader babel-loader @babel/core
, @babel/preset-env
是轉換外掛的預設組合,@babel/plugin-transform-runtime
用來解決一些瀏覽器不支援的方法和物件問題
npm i @babel/runtime -S
npm i babel-loader @babel/core @babel/preset-env @babel/plugin-transform-runtime -D
然後我們在根目錄新建一個配置檔案.babelrc
,使用@babel/preset-env
提供的外掛集合能完成大部分的工作了,targets
表示我們的程式碼要執行到哪些平臺上面,更為詳細的請點選
{
"presets": [
[
"@babel/preset-env",
{
"useBuiltIns": "usage",
"corejs": 3,
"targets": {
"browsers": [ "ie >= 8", "chrome >= 62" ]
}
}
]
]
}
然後修改一下webpack.dev.js
module: {
rules: [
... ...
{
test: /\.js$/,
use: [{
loader: 'babel-loader',
}]
}
]
}
為了驗證程式碼是否轉換成功,我們在index.js
中新增程式碼
const say=(msg)=>{
console.log(msg);
}
然後使用命令npm run envdev
,並開啟source map 檢視原始檔,可以發現箭頭函式已經被轉換了
本節的內容就是這些,下一次將會有個簡單的實戰,對於webpack還有很多要學習的地方,比如打包優化、外掛編寫等等學完基礎以後,這些就需要你自己去探索