前言
webpack前端工程中扮演的角色越來越重要,它也是前端工程化很重要的一環。本文將和大家一起按照專案流程學習使用wbepack,媽媽再也不用擔心我不會使用webpack,哪裡不會看哪裡。這是一個由淺入深的文章。
工程化
這裡是一個專案工程化,規範化的設定,如果是初次使用webpack的同學還是最後在看這一塊知識
現在vue、react等腳手架都會自動將開發環境使用的webpack的配置檔案和生產環境的配置檔案分開,將壓縮程式碼,新增hash控制版本等操作放在專案上線時執行,這樣避免了在開發階段打包時間過長的問題。比如像這樣,把兩個環境的配置檔案分開。
下面來看下兩個配置檔案的內容(我是用的typescript開發react,內容請忽略)
開發環境:
生產環境:
可以看到,開發環境增加了幾個外掛,這樣做的好處就是更加工程化,規範化,降低開發環境的打包時間,程式碼維護性也更高。
分開寫配置檔案就要涉及到使用命令執行不同的配置檔案,我們可以使用npm的指令碼命令,我們可以在package.json中找到scripts,新增如下命令"build": "NODE_ENV=production webpack --config ./webpack.production.config.js --progress"
給大家解釋下這個命令的意思
- NODE_ENV=production 就是將執行環境設定成生產環境
- webpack --config 就是執行webpack的配置檔案
- ./webpack.production.config.js 是要執行的指定位置的檔案,這個路徑是相對根目錄來說的
- --progress 是編譯過程顯示程式百分比的
如果你不追求規範化和工程化,我們就寫一個配置檔案就好,這裡沒有硬性要求。下面我們來講webpack的具體配置
開始
在我們對於webpack不是特別熟練的時候,我們可能不會寫全配置檔案,往往是用到什麼再去新增,下面我們就按照這個步驟徹底學會使用webpack。
module.exports = {
entry: './src/index.js' // 這裡是專案入口檔案地址
ouput: {
path: __dirname + "/dist", // 這裡是專案輸出的路徑,__dirname表示當前檔案的位置
filename: "js/"+"[name].js" // 這裡是生成檔案的名稱,可起你想要的名字
}
}複製程式碼
loader
這就是我們最初一個骨架,下面我們再新增一些配置,比如你使用的是react,那麼你就需要新增react的相關loader,這裡以typescript編寫的react為例。
module.exports = {
entry: './src/index.js' // 這裡是專案入口檔案地址
ouput: {
path: __dirname + "/dist", // 這裡是專案輸出的路徑,__dirname表示當前檔案的位置
filename: "js/"+"[name].js" // 這裡是生成檔案的名稱,可起你想要的名字
},
module: {
rules: [
{ test: /\.tsx?$/, loader: "awesome-typescript-loader" },
{ enforce: "pre", test: /\.js$/, loader: "source-map-loader" }
]
},
}複製程式碼
css/css預處理語言(less、sass、stylus)
webpack是將一個個檔案分拆成一個個模組(module)來進行編譯打包的,我們所有的處理檔案內容的東西都要放在module裡,rules即規則。
rules裡面的兩個loader都是編譯.tsx檔案及處理錯誤資訊的。
在你寫好了元件之後,你需要開始編寫樣式,但無論是css還是使用less、sass等預處理語言,webpack都是無法直接處理的,我們安裝並使用相應的loader。下面以less和css為例。
{test: /\.(less|css)?$/, loader: ["style-loader", "css-loader", "less-loader"]}複製程式碼
webpack會按照從右到左的順序執行loader,我們新解析less,之後進行css的打包編譯。如果你不適用less等預處理語言,安裝css-loader和style-loader即可。
- style-loader 將css插入到頁面的style標籤
- css-loader 是將@import 和 url() 轉換成 import/require()的
- less-loader 是將less檔案編譯成css
postcss解決css相容問題
寫到這裡我們會突然想到一個點,就是css樣式的相容性問題,靠人工去寫的話,你心裡可能會有一句mmp不值當講不當講,哈哈,我們必須使用postcss來解決這個問題。
postcss是目前css相容性的解決方案,會自動幫我們加入字首,以使css樣式在不同的瀏覽器能相容,這裡安裝使用postcss-loader
{ test: /\.(less|css)?$/, loader: ["style-loader", "css-loader", "less-loader", "postcss-loader"]}複製程式碼
postcss-loader要寫在最後(其實只要放在css-loader之後就可以),寫到這你以為就可以了嗎?只能說 too young,postcss解決相容性問題主要靠的其實是它的外掛autoprefixer,我們還需要在根目錄寫一個postcss.config.js的配置檔案,如下
module.exports = {
plugins: [
require('autoprefixer')
]
};複製程式碼
寫到這,我們就不用再擔心css相容性問題了,就想使用babel檔案一樣,這個檔案會自動解析,我們不需要管它。
svg圖片的使用
我們在開發時,往往會遇到一些圖示圖片在不同情況下會失真,以及資源過多,我們需要減小圖示類圖片的大小,這時我們就需要引入svg,國內可能都會去使用阿里的iconfont庫,從而引入svg圖示,解決上面的問題
我們開啟下載的素材資料夾,發現裡面有一些.woff、.svg、.eot的檔案,我們要想使用svg的圖示還必須依賴這些檔案,這時webpack不支援這些檔案,我們需要引入新的loader
{ test: /\.(woff|svg|eot|ttf)?$/, loader: "url-loader" }複製程式碼
下面我們就能愉快的使用svg圖示了,不存在失真的情況,同時會很小
webpack-dev-server
寫到這,我們可能不斷的打包webpack了,太麻煩了,於是乎webpack-dev-server應運而生。它是webpack提供的伺服器,我們使用npm i webpack-dev-server --save-dev來安裝。
我們其實在命令列中敲擊webpack-dev-server --open就可以開啟,預設是localhost:8080開啟,現在我們不需要在重複使用webpack命令打包,安裝。
值得注意的是,webpack-dev-server打包的檔案會存在記憶體中,所以在index.html中引入檔案的時候就要如下,這裡是預設輸出檔案是bundle.js
<script src="bundle.js"></script>複製程式碼
今天我們不重點講webpack-dev-server,以後我會再寫文章,深入的講解其使用。
可能我們在開發階段只用到了這幾個功能,下面我們來講一下專案上線的準備。
生產環境
優化
壓縮js程式碼
我們打包完成的專案往往比較大,包含很多空格,佔用了很大空間,這時我們要通過壓縮js來減小檔案體積。webpack自帶了UglifyJsPlugin外掛來壓縮js程式碼,使用如下
plugins: [
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
}
})
]複製程式碼
我們的外掛統一放在export.modules = {}的plugins裡面,它是一個陣列,使用外掛時new 一個例項即可。這裡我們使用到webpack例項,所以要在配置檔案頭部引入webpack,即var webpack = require('webpack');
拆分檔案
我們在使用的js庫如vue或者react等的時候,webpack會將它們一起打包,react和react-dom檔案就好幾百k,全部打包成一個檔案,可想而知,這個檔案會很大,使用者在首次開啟時就往往會出現白屏等待時間過長的問題,這時,我們就需要將這類檔案抽離出來。
externals: {
"react": "React",
"react-dom": "ReactDOM"
},複製程式碼
這裡我們會用到externals,它和plugins是平級。左側key表示依賴,右側value表示檔案中使用的物件。比如在react開發中,我們常常會這樣在檔案頭部寫import React from 'react',這裡大家可以和上面對號入座下。
這裡我們就需要對這個檔案進行單獨的引入使用了,在index.html中新增如下程式碼
<script src="./node_modules/react/umd/react.xxx.js"></script>
<script src="./node_modules/react-dom/umd/react-dom.xxx.js"></script>複製程式碼
寫到這,我們就已經將檔案拆分了。
不過,我們在專案上線的時候不可能會帶有node_modules,所以我們就需要使用一個copy外掛將react和react-dom檔案複製出來
new CopyWebpackPlugin([ // from是要copy的檔案,to是生成出來的檔案
{ from: "node_modules/react/umd/react.xxx.js", to: "js/react.min.js" },
{ from: "node_modules/react-dom/umd/react-dom.xxx.js", to: "js/react-dom.min.js" }
{ from: "public/favicon.ico", to: "favicon.ico" }
])複製程式碼
這樣我們的index.html檔案中就要寫成下面這種形式
拆分css
我們也可以將css檔案單獨拆分出來,這樣的好處就是打包的css檔案我們可以放到cdn上,然後快取到瀏覽器客戶端中。這樣就儘可能的減小檔案的體積,以及不必要的資源重新載入,浪費頻寬。
我們要先安裝外掛
npm install extract-text-webpack-plugin --save-dev
配置檔案新增對應配置
var ExtractTextPlugin = require("extract-text-webpack-plugin");
plugins裡面新增外掛
new ExtractTextPlugin("styles.css")
下面是具體的使用
module.exports = {
// entry和output自動省略
module: {
loaders: [{
test: /\.css$/,
loader: ExtractTextPlugin.extract('style-loader',
'css-loader!postcss-loader') // 這裡我目前使用less還沒有成功
}]
},
postcss: function() {
return [autoprefixer, cssnext, precss, cssnano]
},
plugins: [
new ExtractTextPlugin('./css/[name].min.css') // 生成到css資料夾下
]
}複製程式碼
webpack會將所有引用到的css檔案打包,最終生成./css/[name].min.css檔案。
圖片處理
這裡對圖片進行base64進行轉碼同樣是減小資源的體積
安裝 url-loader
npm install url-loader --save-dev
在modules的rules裡面新增
{
test: /\.(png|jpg)$/,
loader: 'url?limit=8192&name=images/[hash:8].[name].[ext]'
}複製程式碼
limit 設定一個閾值,小於這個值得圖片就會自動啟用 base64 編碼的圖片,大於這個值的圖片會打包到name 這引數對應的路徑,圖片名稱就會包括8位md5編碼 name 對應檔案本來名稱,ext 對應副檔名
瀏覽器快取資源
我們的後臺會給資源設定Cache-Control: max-age=秒替代,來對資源進行快取時間的設定,這使得我們在重新整理頁面之後會去快取中載入資源,但是存在一個問題,就是,一旦我們更新版本之後,客戶沒有去清除快取,同時快取還沒有過期的情況下,就無法載入到最新的資源。這時我們就需要hash值來進行版本控制
我們通常這樣做
output: {
path: __dirname + "/dist",
filename: "[name][hash].js"
}複製程式碼
給輸出檔案加上[hash]來新增hash值,這樣就可以做到使用者載入html裡會去載入對應hash值得打包檔案,比如下面這樣
<script type="text/javascript" src="main3d1cb903f77dad5737e9.js"></script>複製程式碼
打包出來的js檔案是這樣
這樣就能解決這個問題了。
還有最後一項
我們不可能每次都去手動複製一個index.html到打包好的dist檔案中,我們會使用一款外掛html-webpack-plugin
它可以自動新增html檔案到dist檔案中,同時它會自動新增js檔案並帶有hash值
引入外掛
var HtmlWebpackPlugin = require('html-webpack-plugin');
使用外掛
new HtmlWebpackPlugin({
template: path.join(__dirname, 'src/index.tmpl.html'),
filename: 'index.html'
})複製程式碼
這裡給大家解釋下,template是模板,我們在很多情況下,生產環境和開發環境不同,導致index.html引入的資源路徑不同,這是為了改來改去,我們可以建立一個模板,它指定編譯時我們copy的index.html檔案。filename是最終生成的檔名。
模板檔案如下
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<link rel="icon" href="favicon.ico">
<title>Projection-Web</title>
</head>
<body>
<div id="root"></div>
<script src="js/react.min.js"></script>
<script src="js/react-dom.min.js"></script>
</body>
</html>複製程式碼
生成的index.html檔案如下
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<link rel="icon" href="favicon.ico">
<title>Projection-Web</title>
</head>
<body>
<div id="root"></div>
<script src="js/react.min.js"></script>
<script src="js/react-dom.min.js"></script>
<script type="text/javascript" src="js/main3d1cb903f77dad5737e9.js"></script></body>
</html>複製程式碼
下面是我打包編譯的dist資料夾內容
下面是生產環境的配置檔案(部分)
var CopyWebpackPlugin = require("copy-webpack-plugin");
var HtmlWebpackPlugin = require('html-webpack-plugin');
var webpack = require("webpack");
var path = require('path');
var ExtractTextPlugin = require("extract-text-webpack-plugin");
module.exports = {
entry: "./src/index.tsx",
output: {
path: __dirname + "/dist",
filename: "js/"+"[name][hash].js"
},
resolve: {
extensions: [".ts", ".tsx", ".js", ".json"]
},
module: {
rules: [
{ test: /\.tsx?$/, loader: "awesome-typescript-loader" },
{ enforce: "pre", test: /\.js$/, loader: "source-map-loader" },
{ test: /\.(less|css)?$/, loader: ["style-loader", "css-loader", "less-loader", "postcss-loader"] },
{ test: /\.(woff|svg|eot|ttf)?$/, loader: "url-loader" }
]
},
externals: {
"react": "React",
"react-dom": "ReactDOM"
},
plugins: [
new CopyWebpackPlugin([
{ from: "node_modules/react/dist/react.js", to: "js/react.min.js" },
{ from: "node_modules/react-dom/dist/react-dom.js", to: "js/react-dom.min.js" },
{ from: "index.html", to: "index.html" },
{ from: "public/favicon.ico", to: "favicon.ico" }
]),
new HtmlWebpackPlugin({
template: path.join(__dirname, 'src/index.tmpl.html'),
filename: 'index.html'
}),
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
}
})
]
};複製程式碼
學好webpack,是一名現代前端開發工程師的基本素養。後續還會深入webpack,謝謝大家