上一篇博客起了個頭,介紹了爲何要用webpack,用webpack能夠作什麼,以及最簡單地配置。這篇博客將結合實際需求,一步一步的引入一些配置,全部內容基於實際需求出發。javascript
上一篇博客說到,entry是webpack打包的入口文件,webpack會從這個入口文件開始,尋找該入口文件的依賴模塊,以及遞歸的尋找依賴模塊的依賴模塊,直到將全部的依賴模塊打包成單個js文件輸出到output配置的路徑。根據不一樣的使用場景,主要有一對1、多對1、多對多等不一樣狀況。html
若是沒有設置entry,那麼其默認值是./src/index.js
,即webpack會自動尋找根目錄下的src文件夾中的index.js當作入口文件vue
經過字符串值能夠直接指定入口文件的路徑,好比上一篇博客中的例子:java
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
path: path.join(__dirname, 'dist'),
filename: 'bundle.js'
}
}
複製代碼
這種狀況就是將一個輸入文件打包到一個輸出文件。webpack
有時爲了業務邏輯更加清晰,咱們的入口文件可能不止一個,這時能夠將多個入口文件放在一個數組中,指定到entry:git
const path = require('path');
module.exports = {
entry: ['./src/index1.js', './src/index2.js'],
output: {
path: path.join(__dirname, 'dist'),
filename: 'bundle.js'
}
}
複製代碼
這時會將./src/index1.js
和./src/index2.js
兩個文件打包成一個文件輸出。github
這種場景其實更常見,好比咱們想作一個SPA,一共有5個page,那麼每一個page應該都是一個單獨的入口文件,最後每一個page都應該打包輸出一個單獨的文件,也就是5個輸入、5個輸出的狀況。這時就要用到對象形式的entry:web
const path = require('path');
module.exports = {
entry: {
index1: './src/index1.js',
index2: './src/index2.js'
},
output: {
path: path.join(__dirname, 'dist'),
filename: '[name].js'
}
}
複製代碼
在output中的filename中,咱們沒有直接指定文件名,而是用[name].js
,這裏的name就是entry中的key值index1和index2,也就是告訴webpack,對entry中的兩個入口文件分別打包,最後分別輸出到dist下的index1.js
和index2.js
。shell
在一個SPA項目中,有可能後面還會增長新的page,按照前面的配置,每次新加一個page,就要把這個page的路徑放到entry裏面去,告訴webpack把新增的這個page打包。顯然這種方式並不完美,能夠有更懶的辦法。爲了便於管理,通常會將全部的頁面都放到pages目錄下,那麼在webpack構建時,只須要讀取這個目錄下的全部js文件,而後動態生成entry對象就能夠了。npm
const path = require('path');
const glob = require('glob');
function getEntries() {
const pagePath = path.resolve(__dirname, 'src/pages');
let entries = {};
// 讀取pages目錄下的全部js文件
let files = glob.sync(pagePath + '/*.js');
// 根據文件名,生成entry對象
files.forEach(item => {
let filename = item.split(path.sep).pop().split('.')[0];
entries[filename] = item;
})
return entries
}
module.exports = {
entry: getEntries(),
output: {
path: path.join(__dirname, '/dist'),
filename: '[name].js'
}
}
複製代碼
這樣,就能夠一勞永逸,之後添加的全部頁面均可以自動打包了。
你們都知道瀏覽器是有緩存機制的,若是是同一個js文件,即便服務器上已經存在更新的版本,瀏覽器仍有可能從本地緩存讀取,從而不能拿到最新的結果。要解決這個問題,就須要讓每次打包生成的js文件名不同,而後讓HTML去引用這個新名稱的js文件,從而繞過瀏覽器的緩存。修改文件名最簡單的方法就是在文件名後面加上md5戳,在webpack中這很容易實現
modules.exports = {
entry: getEntries(),
output: {
path: path.join(__dirname, '/dist'),
filename: '[name].[hash].js'
}
}
複製代碼
只用在filename後面加上[hash]
,最後打包生成的js文件名就會帶上md5戳。好比:
index1.20e52ce145885ab03243.js
index2.20e52ce145885ab03243.js
複製代碼
若是不但願後面的md5戳那麼長,也能夠指定md5戳的長度:
modules.exports = {
entry: getEntries(),
output: {
path: path.join(__dirname, '/dist'),
filename: '[name].[hash:5].js'
}
}
複製代碼
這樣最後打包生成的js文件的md5長度只有5位:
index1.20e52.js
index2.20e52.js
複製代碼
hash的計算方法是基於整個項目的,只要整個項目中的任何一個文件發生變化,生成的md5戳就會改變,即便真正的入口js及其依賴並無發生變化。此外,全部的輸出文件都共用這一個md5戳的值。這種方式有些過於粗暴,對瀏覽器的緩存機制很不友好。咱們但願的是,若是文件有改動,文件名後面的hash值改變,若是沒有改動,則保持不變,瀏覽器直接從緩存讀取。這樣既保證了及時更新,又保證了性能。
chunkhash基於每一個入口文件及其依賴進行計算,每一個輸出文件的md5戳只跟本身的依賴文件有關,因此每一個輸出文件的md5戳都是不一樣的,而且只有本身的入口文件或依賴文件發生改變時纔會變化。
modules.exports = {
entry: getEntries(),
output: {
path: path.join(__dirname, '/dist'),
filename: '[name].[chunkhash:5].js'
}
}
複製代碼
最後輸出的結果:
index1.f85c4.js
index2.dd9e3.js
複製代碼
顯然,chunkhash的方式更加理想。
每次打包生成的js文件名都會不同,那麼index.html
中的script標籤每次都要去改引入的文件名,這也太麻煩了。一樣的,webpack能夠幫咱們解決這個問題。前面的博客中提到,webpack的強大之處在於強大的生態系統,提供了不少有用的插件。在這裏,咱們就可使用插件來幫咱們實現。
首先,安裝這個插件:
npm i html-webpack-plugin -D
複製代碼
而後,在webpack配置文件中使用這個插件:
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
...
plugins: [
new HtmlWebpackPlugin({
// options配置
})
]
}
複製代碼
這時候再執行npm run build
,就會看到dist文件下會自動生成一個index.html
,而且這個Html文件會自動引入文件名變化的index.[md5].js
文件。直接將這個文件拖到瀏覽器中就能夠顯示。
若是不作任何配置,html-webpack-plugin會建立一個新的空白HTML(官方文檔說會默認尋找src/index.ejs
,不過我在實測中未實現,有多是我哪一個地方配置不對),只是在這個HTML中引入了打包後的js文件,沒有設定任何的DOM。這種狀況是合理的,由於當咱們用webpack時,不少時候HTML自己就是空白的,全部的DOM都是經過js渲染出來而後掛載上去的,好比常見的vue。能夠經過title去設定這個HTML的title標籤的內容。固然也能夠指定一個模板HTML,html-webpack-plugin會在這個HTML中自動引入打包後的js文件。options的主要配置有:
plugins: [
new HtmlWebpackPlugin({
// 指定index.html做爲模板,即讓index.html自動引用打包後的js文件
template: 'src/index.html',
// 輸出到dist目錄下的index1.html
filename: 'index1.html',
// 讓index.html只引用dist目錄下的page1.[hash].js文件,注意這裏只用寫文件名的前綴,若是寫成page1.js會直接在dist文件夾下找page1.js文件,而不能找到帶hash的結果
chunks: ['page1']
})
]
複製代碼
若是但願有多個HTML,那麼就能夠設置多個html-webpack-plugin:
plugins: [
// 第一個頁面,index1.html,引用page1.js打包後的文件
new HtmlWebpackPlugin({
template: 'src/index.html',
filename: 'index1.html',
chunks: ['page1']
}),
// 第二個頁面,index2.html,引用page2.js打包後的文件
new HtmlWebpackPlugin({
template: 'src/index.html',
filename: 'index2.html',
chunks: ['page2']
})
]
複製代碼
固然,若是頁面個數是動態變化的,也能夠用多entry類型的形式讀取文件夾下的全部文件。更多的配置能夠Github
webpack每執行一次,就會在dist目錄下生成一些文件。若是此次生成的文件和上次是同名的,則會直接覆蓋,問題不大。但若是是不一樣名的,則會在dist文件中累加,最後致使dist文件夾中存在不少沒必要要的文件。例如上面咱們加上md5戳以後,每次打包生成的輸出文件名並不相同,這就會形成dist文件夾的累積。即便不是用md5戳,也會有相似場景。好比開始我在pages下有一個page3.js
,打包後在dist目錄下生成了page3.js
,後來我不須要這個頁面了,把pages下的page3.js
刪除了,執行webpack,但此時dist目錄下的page3.js
依然存在。顯然這些不是咱們但願的。一種解決方法是每次執行webpack前手動清空dist,不過這也太麻煩了,這種麻煩的事情固然須要webpack來替咱們作。
首先,須要安裝這個插件
npm i clean-webpack-plugin
複製代碼
而後,在配置文件中使用這個插件
const {CleanWebpackPlugin} = require('clean-webpack-plugin');
modules.exports = {
...,
plugins: [
new CleanWebpackPlugin()
]
}
複製代碼
這樣,每次執行webpack時就會自動清除dist目錄下的全部文件。
到目前爲止,每次修改js文件後,都須要手動的執行npm run build
來啓動webpack進行編譯。一樣的,這種機械性的重複工做很煩,應該交給webpack來作纔對。只須要在執行webpack時加上--watch
啓動監聽模式就能夠了,直接在package.json
中修改:
"scripts": {
"build": "webpack --watch"
}
複製代碼
這樣只須要執行一遍npm run build
,後面只要咱們修改了入口文件或其依賴文件中的任意一個,都會自動從新執行webpack打包。注意,若是是修改了webpack的配置文件webpack.config.js
,並不會自動從新執行,仍是要手動執行如下。
雖然修改文件後能夠自動打包了,但仍是要手動刷新一下瀏覽器才能看到效果,這步操做也仍是有點煩,一樣的交給webpack來作吧。
咱們這裏先考慮只有一個輸出的狀況,即html-webpack-plugin爲默認配置:
plugins: [new HtmlWebpackPlugin()]
複製代碼
webpack-dev-sever
插件npm i webpack-dev-server -D
複製代碼
module.exports = {
devServer: {
// 指定dist文件夾做爲服務器的目錄
contentBase: './dist'
}
}
複製代碼
"scripts": {
"build": "webpack --watch",
"start": "webpack-dev-server --opem"
}
複製代碼
而後執行npm run start
,瀏覽器窗口就會自動打開,顯示index.html引用全部js文件的結果。這是隻要改變index.html
或者任意一個引用的js文件,瀏覽器都會自動刷新以顯示最新的結果。
單個輸出的狀況比較簡單,dist目錄下只有一個index.html
文件,瀏覽器一打開就會顯示這個文件。可是,若是是上面「動態生成HTML」一節描述的多個html文件,DevSever確定沒法知道顯示哪個了,這時有如下2種辦法:
Webpack-dev-server通常默認打開的是localhost:8080
,在contentBase中咱們配置的就是dist文件夾,因此這個地址等同於dist文件夾,若是輸入localhost:8080/index1.html
就會顯示index1.html
,輸入localhost:8080/index2.html
就會顯示index2.html
直接配置openPage,告訴webpack-dev-server默認打開哪一個:
devServer: {
contentBase: "./dist",
// 告訴webpack-dev-server默認打開index2.html
openPage: './index2.html'
}
複製代碼
在已經使用了webpack-dev-server的狀況下,修改文件已經不須要手動刷新了,可是,瀏覽器自動刷新也會有一些問題:好比在調試一個表單驗證問題,已經填寫了一些信息,若是瀏覽器自動刷新,會直接將以前填寫的表單內容清空。而webpack的熱更新(HMR, Hot Module Replacement),就可讓瀏覽器在不刷新的狀況下直接更新瀏覽器頁面。
使用HotModuleReplacementPlugin,它包含在webpack中。在配置文件中添加:
const webpack = require('webpack');
module.exports = {
plugins: [new webpack.HotModuleReplacementPlugin()]
}
複製代碼
在以前devServer中設置hot爲true
devSever: {
contentBase: './dist',
hot: true
}
複製代碼
在入口文件index.js底部添加對子模塊的監聽
if (module.hot) {
module.hot.accept();
}
複製代碼
這樣,若是再修改子模塊a.js中的文件時,在不刷新瀏覽器的狀況下就能夠更新。
到目前爲止,都是介紹如何用webpack打包從而進行開發,卻忽視了一個問題於webpack將多個文件打包成了單個文件的。最後在瀏覽器中,咱們能看到的也就是打包後的這個文件。若是是打斷點調試,咱們固然但願是能在打包以前原始模塊中,這樣定位問題和修改也比較方便。爲了實現這個功能,webpack中提供了devtool配置,只須要設置以下:
module.exports = {
devtool: 'source-map'
}
複製代碼
這樣,在瀏覽器的控制檯中就能夠看到打包以前的原始模塊文件。
一般,js代碼都要分開發模式和生產模式。好比,在開發模式中,不用太糾結代碼的性能,更加劇視代碼的可讀性,而在生產模式中,代碼通常須要進行優化和壓縮等。在webpack4中,能夠經過mode來配置development模式和production模式,主要有兩種配置方法:
直接在配置文件中指定mode
module.exports = {
mode: 'development'
}
複製代碼
在運行webpack時增長--mode
,能夠在package.json
中添加
"scripts": {
"dev": "webpack --mode development",
"prod": "webpack --mode production"
}
複製代碼
這時,運行npm run dev
就是開發模式,npm run prod
就是生產模式
分別看一下dist目錄下輸出的文件,能夠發現production模式下的代碼體積更小,dev模式下的代碼可讀性更強。
在webpack 4中,已經能夠經過mode來指定不一樣生產環境下代碼的編譯模式了,但這仍是不夠的。好比,在開發環境下,咱們但願開啓devtool調試,而在生產模式下就不須要。像這樣的區分配置還有不少,若是每次都是去修改webpack.config.js
,顯然很是麻煩。一種簡單的方法是,咱們建一個build文件夾,在這個文件下下放三個配置文件。爲了整合,先安裝一個叫webpack-merge
的插件,其主要做用是合併兩個webpack配置文件:
npm i webpack-merge -D
複製代碼
而後分別寫三個配置文件:
webpack.base.config.js
:這個文件用來存放公共的配置,也就是在開發環境和生產環境都要用到的
module.exports = {
// 公共配置,如entry/output
}
複製代碼
webpack.dev.config.js
:開發環境下的獨有配置
const merge = require('webpack-merge');
const baseConfig = require('./webpack.base.config')
module.exports = merge(baseConfig, {
mode: 'development',
devtool: 'source-map',
devServer: {
contentBase: './dist',
// 在開發模式下,默認打開index1
openPage: './index1.html',
hot: true
},
})
複製代碼
webpack.prod.config.js
:生產環境下的獨有配置
const merge = require('webpack-merge');
const baseConfig = require('./webpack.base.config');
module.exports = merge(baseConfig, {
mode: 'production',
devServer: {
contentBase: './dist',
// 在生產模式下,默認打開index2
openPage: './index2.html',
hot: true
},
})
複製代碼
能夠看到,base只是一個公共文件,真正傳到webpack使用的是dev和prod。那麼如何傳到webpack呢?在webpack或者webpack-dev-server運行時,能夠經過--config
指定配置文件。因此,咱們直接修改package.json
"scripts": {
"dev": "webpack-dev-server --open --config ./build/webpack.dev.config.js",
"prod": "webpack-dev-server --open --config ./build/webpack.prod.config.js"
}
複製代碼
此時,執行npm run dev
,就會按照webpack.dev.config.js
的配置,打開index1.html
,而且開啓debug;執行npm run prod
,就會按照webpack.prod.config.js
的配置,打開index2.html
。