【30分鐘】吃透webpack,也許這一篇就夠了
使用webpack前的準備
1、初始化一個前端專案
為了方便之後自己更好的使用這個webpack_starter,引入git的支援,一是可以把一些通用的東西放在主分支,二是可以把後面不同的配置支援可以通過branch
或者tag
的方式分門別類。
- 在github初始化一個
webpack_starter
專案,如下圖所示,初始化.gitignore
支援Node
語言
#clone專案到本地
git clone https://github.com/mpandar/webpack_starter.git
cd webpack_starter
- 利用yarn初始化專案(當然同樣可以使用npm,無太大差異,此處不再補充npm使用方法)
初始化過程按照提示完成即可,唯一注意的是entry point
,這是webpack進行打包時的入口檔案,預設是根目錄下的index.js
,不過通常情況下,我們的原始碼都是在src
目錄下,所以修改為src/index.js
> yarn init
yarn init v1.3.2
question name (webpack_starter):
question version (1.0.0): 0.1.0
question description: a webpack start project
question entry point (index.js): src/index.js
question repository url (https://github.com/mpandar/webpack_starter.git):
question author (mpandar <mshp_****@126.com>):
question license (MIT):
question private:
success Saved package.json
✨ Done in 53.55s.
2、安裝webpack
單獨安裝與全域性安裝對於webpack的使用並無太大差異,但推薦即使全域性安裝以後,仍要在專案中進行單獨的安裝,方便專案移植。否則可能會導致全域性安裝的webpack版本與專案中的配置檔案可能存在不匹配。當然單獨安裝後,使用一些npm或yarn命令,它們會優先使用本地安裝的webpack
#全域性安裝
yarn global add webpack
#單專案使用
yarn add webpack
3、初始化專案目錄
|---
|--dist //存放webpack打包後相關檔案
|--src //存放專案原始碼
|--index.js
|--config //專案相關的配置檔案
|--webpack.config.js //webpack預設讀取專案根目錄下的webpack.config.js檔案作為配置資訊,為了規範化移入到config目錄下
|--package.json
瞭解webpack配置檔案webpack.config.js
當然,即使沒有配置檔案,直接執行webpack命令,同樣可以直接對js檔案完成打包工作,這裡是一篇分析webpack打包後的程式碼的文章:簡要分析webpack打包後代碼,其中用到的一個新命令npx,很簡單,介紹點這裡
#直接打包
npx webpack src/index.js dist/bundle.js
為了應對更靈活的使用場景,webpack支援配置檔案,並且預設情況下,在專案根目錄下,如果存在webpack.config.js
檔案,那麼webpack會主動讀取該檔案作為配置內容,不過Demo下,為了更加符合我們的目錄規範,我們將config檔案移到了config
目錄下
//當配置檔案內容為空時,執行該命令會提示`Configuration file found but no entry configured`
npx webpack src/index.js --config config/webpack.config.js
接下來,讓我們看一下一個webpack配置檔案,最簡單隻需要包含entry(定義入口檔案)和output(定義打包輸出檔案)這兩個部分:
const path = require('path');
const base = path.join(__dirname, '..')
module.exports = {
entry: path.resolve(base, 'src', 'index.js'),
output: {
filename: 'bundle.js',
path: path.resolve(base, 'dist')
}
};
注:使用path模組只是為了程式碼清晰,你完全可以不用,直接用__dirname+'/../src'
類似程式碼拼接
//這樣就不需要在命令列定義輸入輸出檔案啦
npx webpack --config config/webpack.config.js
除了entry
和output
,webpack中最常見的就是module
、resolve
、plugins
,大致結構如下:
module.exports = {
entry: path.resolve(base, 'src', 'index.js'),
output: {
filename: 'bundle.js',
path: path.resolve(base, 'dist')
},
devtool: 'eval-source-map',
devServer: {
contentBase: path.resolve(base, 'dist'),
historyApiFallback: true,
inline: true,
proxy: {
"/api": "http://localhost:8000"
}
},
module: {
rules: [
]
},
resolve: {
},
plugins: [
]
};
當然瞭解webpack最好的地方永遠是官方文件,傳送門,接下來,自然是在目前配置的基礎上,增添更多令人興奮的特性
為開發增加更多利器
生成Source Map,為除錯助力
開發離不開除錯,但經過編碼後的程式碼並不利於除錯,很找到出錯的地方對應的你寫的程式碼,而Source Maps就是來幫我們解決這個問題的。
而webpack支援Source Maps僅僅是增加一行devtool
配置選項,具體配置選項可以看這裡的官方文件,其中兩個選項eval-source-map
與source-map
是比較常用的選項,前一個選項推薦僅僅用在開發環境,而後一個通常在一些第三方庫中,提供給開發者除錯使用。當然對於任何上線專案,實際上都推薦使用*.min.js並不使用Source Map以加快網路載入。
module.exports = {
entry: path.resolve(base, 'src', 'index.js'),
output: {
filename: 'bundle.js',
path: path.resolve(base, 'dist')
},
devtool: 'eval-source-map'
}
自動監控程式碼更新,自動編譯,自動瀏覽器重新整理
作為開發者,總不希望把時間浪費在執行命令和重新整理頁面上,webpack提供一個單獨的元件webpack-dev-server
為我們提供一個基於nodejs的本地伺服器、檔案修改監控及編譯以及瀏覽器自動重新整理等特性。
首先是安裝:
yarn add webpack-dev-server--dev
詳細配置引數可查閱官方文件,常使用引數如下:
- contentBase 指定專案根目錄
- historyApiFallback 主要應用在單頁應用的開發場景,它依賴於HTML5 history API,如果設定為true,所有的跳轉將指向index.html
- inline 主要是解決了自動瀏覽器自動重新整理問題
- lazy 預設為false,若開啟後,則webpack-dev-server不再監測檔案變化自動編譯,只有等到我們手動重新整理瀏覽器的時候才會編譯檔案
- proxy 可以有效的解決開發階段的跨域問題,畢竟不是所有的前端專案,都有獨立的nodejs作為中介軟體去請求服務,更多的還是把前端專案編譯後與後端服務部署在一起。而開發階段又相對獨立,這時候就可以利用proxy將前端請求轉發到後端測試伺服器,不影響開發
module.exports = {
entry: path.resolve(base, 'src', 'index.js'),
output: {
filename: 'bundle.js',
path: path.resolve(base, 'dist')
},
devtool: 'eval-source-map',
devServer: {
contentBase: path.resolve(base, 'dist'),
historyApiFallback: true,
inline: true,
proxy: {
"/api": "http://localhost:8000"
}
}
}
小插曲,本來測試proxy的時候,自己利用php -S localhost:8000快速起了一個監聽程序,但是訪問前端的時候卻提示轉發去請求被拒絕,後來發現webpack-dev-server去轉發請求的時候是把localhost轉化為了地址,即127.0.0.1:8000,所以在php啟動監聽程序時候,需要使用php -S 127.0.0.1:8000
或者php -S 0.0.0.0:8000
監聽所有網絡卡地址
webpack-dev-server的使用同webpack基本執行一樣,只是webpack-dev-server是一個不會退出的程序,並自動監控檔案變化等(Ctrl+C退出)
npx webpack-dev-server--config config/webpack.config.js
更方便的打包命令
使用npx webpack --config config/webpack.config.js
進行打包實際上已經很方便了,但是當我們需要又有開發環境的配置,又有生產環境的配置,甚至還要為命令增加其他的環境變數的時候,這個命令簡直是又臭又長,我們總不能每次都輸入這個繁瑣的命令,其實我們可以利用npm scripts,這裡是一篇阮一峰大神對npm指令碼的介紹
//package.json
{
"scripts": {
"build": "webpack --config config/webpack.config.js",
"dev": "webpack-dev-server --config config/webpack.config.js"
}
}
npm run build
npm run dev
需要注意的是在配置build&dev指令碼的時候,我們並沒有用npx
命令,實際上,在npm指令碼中的命令,npm預設都是優先查詢本專案下node_modules下是否存在對應的模組及命令,如果沒找到,才會查詢全域性的命令
多檔案入口
通常一個專案中,並不是只有一個js檔案,而entry預設支援多檔案入口,修改output跟隨入口檔名字命名即可,簡單做如下修改:
module.exports = {
entry: {
index: path.resolve(base, 'src', 'index.js'),
main: path.resolve(base, 'src', 'main.js')
},
output: {
filename: 'js/[name].js',
path: path.resolve(base, 'dist')
}
}
同時建立一個簡單的main.js進行測試,再次編譯會發現在dist/js目錄下存在編譯後的index.js和main.js檔案
一切皆為Module
webpack令人興奮的一個特性就是模組化,易於擴充套件其功能。webpack支援大量的模組匯入方法,比如ES6(import)、CommonJS(require)、AMD等規範,並且加入了一些自由方法,具體的可以看官方說明文件。
用通俗的語言描述就是,webpack通過某個入口,去匹配各種模組匯入規範,發現一個模組就根據對應配置尋找對應的loader去處理,如此往復,直到處理完畢所有依賴。
使用起來就是在modules欄位中的rules(老版本名字為loaders,為保證相容性,還是支援這個欄位的)中配置test,去匹配檔案(js、css、圖片資源等等),然後把這個檔案交給合適的loader處理即可,所以但凡新出的框架,如果用到了獨特的語法功能,都會配套提供對應的loader工具
使用ES6等新特性
目前雖然瀏覽器對ES6新特性的支援度都非常高,但仍是有部分場景下,我們只能執行ES5的程式碼,這時候就需要利用到js轉碼屆的特斯拉Bebel及其外掛了
yarn add babel-loader babel-core babel-preset-env
module: {
rules: [
{
test: /\.js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
presets: ['env']
}
}
}
]
},
顯然test
中匹配了所有的js檔案,exclude
欄位去除了專案中依賴庫裡的檔案,use
則是配置對應的loader。其中對於options,是作為引數傳遞給babel-loader的,babel的相關引數可以參考babel的官方網站,其中presets作為最主要的引數,告訴babel按照那種規則去解析程式碼,當然env
是一個組合,包含es2015、es2016等,當presets引數包含多個值時,babel的處理規則是倒序的,"es2017","es2016"
,babel會先去匹配es2016
的規則。另外,對於babel的配置,也可以通過在根目錄建立.babelrc
方式去配置:
//.babelrc
{
"presets": [
"env"
]
}
當然除了擴充套件js的語法,有時候我們還需要擴充套件js的功能,比如在某些低版本的瀏覽器上執行'Hello World'.includes('Hello')
,這可以使用babel-polyfill
這個元件,點選這裡可以瞭解其使用方法。
CSS
當然,我們可以只讓webpack處理js檔案,繼續在html檔案中通過link
標籤引入css檔案。但顯然webpack希望前端攻城獅們也能像模組化編寫js一樣,進行css的編寫。我們在src下建立css目錄,並建立main.css檔案
/* src/css/main.css */
body{
background-color: deepskyblue; /* 我喜歡填空的藍色 */
}
//src/index.js
import style from './css/main.css'
這時候使用npm run build
會發現編譯報錯,這是因為webpack無法處理載入main.css後的程式碼
ERROR in ./src/css/main.css
Module parse failed: Unexpected token (1:4)
You may need an appropriate loader to handle this file type.
| body{
| background-color: red;
| }
@ ./src/index.js 3:12-37
webpack官方提供css-loader
專門處理匯入的css模組,當然css-loader
也僅僅是完成了模組的匯入處理,使webpack在編譯時候不再報錯,實際上,還需要style-loader
處理匯入後的css資料,自動新增到html的style標籤中。如果你在測試過程中,僅僅新增css-loader
loader,你會發現body實際上並沒有變成背景藍
yarn add style-loader css-loader
//config webpack.config.js module->rules
{
test: /\.css$/,
use: [
{
loader: "style-loader"
}, {
loader: "css-loader",
options: {
modules: true,
localIdentName: '[path][name]__[local]--[hash:base64:5]'
}
}
]
}
當然css-loader
還有一個重要配置選項,modules
引數,它支援匯入的css中的類名自動重新命名,這樣即使在不同元件使用了相同的css類命名,經處理後互相之間也不會出現影響。看下效果:
CSS預編譯工具
比較常見的預編譯工具也就是Sass、Less、Stylus。在使用這三個預編譯工具前,需要安裝其對應的專屬處理程式,比如Sass的處理工具node-sass。為了配合與webpack使用,還要安裝對應的loader,比如sass-loader
yarn add sass-loader node-sass--dev
在webpack.config.js中新增對scss(Sass 3引入的新的語法格式,推薦新專案都用此)檔案的支援,再起強調,webpack中loader執行順序是從右往左,從下往上。
// webpack.config.js
module.exports = {
...
module: {
rules: [{
test: /\.scss$/,
use: [{
loader: "style-loader"
}, {
loader: "css-loader",
options: {
modules: true,
localIdentName: '[path][name]__[local]--[hash:base64:5]'
}
}, {
loader: "sass-loader"
}
}]
}]
}
};
測試一下:
/* src/sass/main.scss */
body {
background-color: blue;
}
.main {
background-color: grey
}
//src/index.js
// import style from './css/main.css'
import style from './sass/main.scss'
let es6 = () => {
console.log('run in es6')
console.log(style)
}
es6();
其他兩種預編譯器可以參考各自官方文件:less-loader、stylus-loader
像處理JS一樣處理CSS -- PostCSS
PostCSS官網介紹是,利用js轉譯css的一個工具。對PostCSS的介紹,我比較認可這篇文章,PostCSS提供了一個解析器,把css轉換為抽象語法樹(AST),當然這個AST是能夠被js處理的,然後交給各種外掛處理後,再將AST轉為css程式碼,所以關鍵是這些外掛能完成哪些功能。
Autoprefixer
Autoprefixer 是一個流行的 PostCSS 外掛,其作用是為 CSS 中的屬性新增瀏覽器特定的字首。
cssnext
cssnext 外掛允許開發人員在當前的專案中使用 CSS 將來版本中可能會加入的新特性。需要注意這個外掛本身包含了Autoprefixer功能,所以如果使用了這個外掛,則不需要Autoprefixer外掛。
當然PostCSS中其實也包含支援Sass、Less等的外掛,這個看個人喜好,不過我本人倒是蠻期待嘗試下cssnext,畢竟大部分特性可能就是未來css支援的特性,提前熟悉下也算是預習。
更多外掛可以讀這篇文章,不再敘述
安裝postcss-loader及其外掛autoprefixer
yarn add postcss-loader autoprefixer --dev
在webpack.config.js
中新增postcss支援,注意postcss處理css檔案的位置
// webpack.config.js
module.exports = {
...
module: {
rules: [{
test: /\.scss$/,
use: [{
loader: "style-loader"
}, {
loader: "css-loader",
options: {
modules: true,
localIdentName: '[path][name]__[local]--[hash:base64:5]',
minimize: false
}
}, {
loader: 'postcss-loader',
options: {
config: {
path: path.resolve(base, 'config', 'postcss.config.js')
}
}
}, {
loader: "sass-loader"
}]
}]
}
};
如上面配置,postcss需要單獨的配置檔案,建立config/postcss.config.js
,新增如下配置:
//config/postcss.config.js
module.exports = {
plugins: {
'autoprefixer': {}
}
}
圖片資源
file-loader提供了對圖片資源的loader功能,並且利用publicPath
選項還能很好的支援cdn,配合url-loader還能對小圖片直接進行數字序列化(DataURL),減少網路請求,提高載入速度。
yarn add url-loader file-loader--dev
增加相關配置;url-loader的預設fallback
loader就是file-loader
為了更直觀就寫了出來,傳遞給file-loader
的引數也只需要寫在options中即可。這樣background-image: url('../image/logo.jpg')
當我們在css檔案中使用這種方式引入圖片時,就會觸發url-loader去處理
//config/webpack.config.js
module.exports = {
...
module: {
rules: [
{
test: /\.(png|jpg|gif)$/,
use: [
{
loader: 'url-loader',
options: {
limit: 8192,
fallback: 'file-loader',
name: '[hash:5].[ext]',
// publicPath: 'https://cdn.j2do.com/',
outputPath: 'images/'
}
}
]
}
]
}
}
雖不是萬能,但卻異常強大的外掛(Plugins)系統
Loader是專注於處理Webpack匯入的資源模組,而外掛是對Webpack功能的擴充套件。除了Webpack內建的外掛,開發社群提供了大量優秀的外掛。當然外掛也是解決問題的,我們還是以問題為導向,去介紹幾款外掛。
利用extract-text-webpack-plugin
分離css到獨立檔案
yarn add extract-text-webpack-plugin --dev
其中ExtractTextPlugin中fallback是指定了如果不需要提取到獨立css檔案中的樣式檔案,則交給style-loade
處理,其他loader配置,跟之前沒有差異。同樣如果是預編譯檔案(Sass、Less等)的話,也只需要增加對應的loader即可
module.exports = {
...
module: {
rules: [
{
test: /\.css$/,
use: ExtractTextPlugin.extract({
fallback: "style-loader",
use: [
{
loader: "css-loader",
options: {
modules: true,
localIdentName: '[path][name]__[local]--[hash:base64:5]'
}
}, {
loader: 'postcss-loader',
options: {
config: {
path: path.resolve(base, 'config', 'postcss.config.js')
}
}
}
]
})
}
]
}
...
plugins: [
new ExtractTextPlugin("css/[name].css")
]
};
再次執行yarn run build
會發現生成了獨立的css檔案
利用html-webpack-plugin
自動生成index.html
等入口檔案
隨著多入口以及css的分離,手動去寫html的入口檔案也是一件麻煩事,這時候html-webpack-plugin
就排上用途了,html-webpack-plugin
能夠根據某個模板檔案自動生成入口的html,包括多個入口,安裝及配置如下:
yarn add html-webpack-plugin --dev
module.exports = {
...
plugins: [
new ExtractTextPlugin("[name].css"),
new HtmlWebpackPlugin({
title: 'Index Page',
template: path.resolve(base, 'src', 'template/index.html.tmpl'),
filename: "index.html",
chunks: ['index']
}),
new HtmlWebpackPlugin({
title: 'Main Page',
template: path.resolve(base, 'src', 'template/index.html.tmpl'),
filename: "main.html",
chunks: ['main']
}),
]
};
建立模板檔案,注意之所以命名為.tmpl
是為了防止 .html 可能會被loader解析,<%= ... %>
將不會被外掛識別,完成變數替換。如下圖,我們的title
是以變數形式在webpack.config.js
中配置。該外掛還支援多種模板檔案,具體可見官方文件
<!-- src/template/index.html.tmpl -->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>
<%= htmlWebpackPlugin.options.title %>
</title>
</head>
<body>
</body>
</html>
程式碼優化壓縮
前面已經將js、css等分離到單獨檔案,接下來就是優化壓縮這些程式碼。
對於css而言,只需要配置css-loader
的minimize
引數為true
即可;當然還可以利用postcss的cssnano
外掛進行程式碼的壓縮和優化,據說cssnano
是目前css壓縮優化中效果最好的工具。
對於js的壓縮,我們藉助於uglifyjs-webpack-plugin
外掛
yarn add uglifyjs-webpack-plugin--dev
module.exports = {
...
plugins: [
new ExtractTextPlugin("css/[name].css"),
new HtmlWebpackPlugin({
title: 'Index Page',
template: path.resolve(base, 'src', 'template/index.html.tmpl'),
filename: "index.html",
chunks: ['index']
}),
new UglifyJsPlugin()
]
};
藉助Eslint自動程式碼規範檢測及格式化
Eslint不再介紹,在webpack下使用Eslint需要如下依賴包:
yarn add eslint eslint-loader babel-eslint eslint-config-standard --dev
其中eslint是必須的,eslint-loader
是連線eslint與webpack的loader,babel-eslint
是一個eslint解析器,使其能支援es6等語法檢測,eslint-config-standard
是Airbnb的規範配置,目前最流行的js規範。
安裝過程中如果有提示[email protected]" has unmet peer dependency "eslint-plugin-import@>=2.2.0"
等等,直接安裝對應的包即可,比如:yarn add eslint-plugin-import --dev
即可
eslint是規範js語法,所以他需要處理的是js檔案,而且應該是先於所有loader去處理js檔案,如果出錯或者不規範則糾正之,這裡可以利用webpack的enforce
屬性,設定eslint檢查,先於其他loader
module.exports = {
...
module: {
rules: [
{
enforce: "pre",
test: /\.js$/,
exclude: /node_modules/,//注意不要檢測node_modules裡面的程式碼
loader: "eslint-loader",
options: {
fix: true //自動修復不規範的程式碼,並不是所有程式碼都能自動修復的,一些縮排,引號等能直接處理
}
}
...
]
}
}
同時還要為eslint增加對應的配置檔案.eslintrc
{
"parser": "babel-eslint",
"extends": "standard",
"rules": {}
}
這時候再嘗試build吧,會發現一些不規範的程式碼被自動修復,當然有些不規範的程式碼,無法自動修復的,會直接導致錯誤;比如,聲明瞭一個函式,卻沒有使用!