webpack的基本使用1
可參考:https://segmentfault.com/a/1190000006178770#articleHeader2
1、webpack的概念
本質上,webpack 是一個用于現代 JavaScript 應用程序的靜態模塊打包工具。當 webpack 處理應用程序時,webpack 從命令行或配置文件中定義的一個模塊列表開始,處理你的應用程序,它會遞歸地在內部構建一個依賴關系圖,其中包含該應用程序需要的每個模塊,然后將所有這些模塊打包成一個或多個 bundle,通常只有一個 - - 可由瀏覽器加載。
在 webpack 看來,前端所有的資源文件(js、json、css、img、less...)都會作為模塊處理。它將根據模塊的依賴關系進行靜態分析,打包生成對應的靜態資源(bundle)。webpack 本身只能理解 JavaScript 和 JSON 文件,這是 webpack 開箱可用的自帶能力。
webpack的五個核心概念:入口(Entry)、輸出(output)、loader、插件(plugin)、模式(mode)。
2、安裝webpack
全局安裝命令如下,但我們并不推薦全局安裝 webpack,因為這會將你項目中的 webpack 鎖定到指定版本,并且在使用不同的 webpack 版本的項目中,可能會導致構建失敗。
npm i webpack -g
本地安裝:
npm i webpack -D
新的版本比如 webpack4.xx+ 還需要安裝webpack-cli,而舊的版本比如3.5.3就不需要安裝webpack-cli(此工具用于在命令行中運行 webpack),應該是因為新的版本webpack和webpack-cli分離了,而舊的版本中webpack包括了webpac-cli。
安裝 webpack-cli:
npm install webpack-cli -g
2.1、版本
我們并不建議安裝最新版本的 webpack,在安裝這些最新體驗版本時要小心,因為它們可能仍然包含 bug,因此不應該用于生產環境。
查看webpack版本命令如下:
npm info webpack
npm info webpack可查看webpack 相關信息,第一條就是版本號:

3、使用webpack
先使用 npm init 初始化一個項目,建立如下目錄結構:

src/index.js 文件代碼:
import _ from 'lodash'; //lodash是一個 JavaScript 實用工具庫 function component() { const element = document.createElement('div'); //引入lodash后就可以使用全局變量 _,如下 _.join() 只是一個操作數組的方法 element.innerHTML = _.join(['Hello', 'webpack'], ' '); return element; } document.body.appendChild(component());
安裝依賴:
npm install webpack webpack-cli --save-dev
npm install --save lodash
dist/index.html 文件代碼:
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>起步</title> <!-- <script src="https://unpkg.com/lodash@4.17.20"></script> --> </head> <body> <!-- <script src="./src/index.js"></script> --> <script src="main.js"></script> </body> </html>
如上,使用 webpack 構建后不需要手動引入 lodash,并且也不需要引入 index.js ,只需要引入構建后生成的 main.js 文件即可。
執行 npx webpack 命令開始構建:
npx webpack
該命令會調用 webpack,webpack 默認會以當前項目根目錄下的 src/index.js 文件為入口,生成一個 bundle 在 dist 文件夾下,并且命名為 main.js 。所以構建過后,我們只需要在 index.html 文件中引入 main.js 文件即可。
3.1、使用配置文件(webpack.config.js)
Webpack擁有很多其它的比較高級的功能,這些功能其實都可以通過命令行模式實現,但非常不方便,所以定義一個配置文件非常有必要。這個配置文件其實也是一個簡單的JavaScript模塊,我們可以把所有的與打包相關的信息放在里面。
我們新建一個 webpack.config.js 文件:

webpack.config.js 文件代碼:
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
filename: 'main.js',
path: path.resolve(__dirname, 'dist'), //path:打包得到的結果文件的輸出路徑。當前代碼是得到了當前文件目錄下的dist文件夾路徑。還可以配置 publicpath,這個是資源的引用路徑。 __dirname - 當前文件目錄 path.resolve() - 路徑拼接方法
},
};
現在我們通過新的配置文件再次執行構建:
npx webpack --config webpack.config.js
如果 webpack.config.js 存在,其實 webpack 命令就會自動默認選擇使用它。但是我們也可以使用 --config 選項來使用任何名稱的配置文件。
3.2、使用npm scripts
調整 package.json 文件,添加一個 npm script:
{ "name": "webpackdemo01", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "build": "webpack" }, "author": "", "license": "ISC", "devDependencies": { "webpack": "^5.26.1", "webpack-cli": "^4.5.0" }, "dependencies": { "lodash": "^4.17.21" } }
現在,可以使用 npm run build 命令,來替代我們之前使用的 npx 命令。使用 npm scripts,我們可以像使用 npx 那樣通過模塊名來直接引用本地安裝的 npm packages。其實跟使用 npx 是一樣的。
4、加載css等資源
模塊 loader 可以鏈式調用。鏈中的每個 loader 都將對資源進行轉換。鏈會逆序執行。第一個 loader 將其結果(被轉換后的資源)傳遞給下一個 loader,依此類推。最后,webpack 期望鏈中的最后的 loader 返回 JavaScript。
應保證 loader 的先后順序,比如我們應該保證'style-loader' 在前,而 'css-loader' 在后。如果不遵守此約定,webpack 可能會拋出錯誤。
4.1、加載css資源
為了在 JavaScript 模塊中 import 一個 CSS 文件,你需要安裝 style-loader 和 css-loader,并在 module 配置 中添加這些 loader:
npm install --save-dev style-loader css-loader
webpack.config.js
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
},
module: {
rules: [
{
test: /\.css$/i,
use: ['style-loader', 'css-loader'],
},
],
},
};
4.1.1、css模塊化
CSS modules意在把JS的模塊化思想帶入CSS中來,通過CSS模塊,所有的類名,id 名默認都只作用于當前模塊。
Webpack對CSS模塊化提供了非常好的支持,只需要在CSS loader中進行簡單配置即可,然后就可以直接把CSS的類名傳遞到組件的代碼中,這樣做有效避免了全局污染。具體的代碼如下
module.exports = {
...
module: {
rules: [
{
test: /(\.jsx|\.js)$/,
use: {
loader: "babel-loader"
},
exclude: /node_modules/
},
{
test: /\.css$/,
use: [
{
loader: "style-loader"
}, {
loader: "css-loader",
options: {
modules: true, // 指定啟用css modules
localIdentName: '[name]__[local]--[hash:base64:5]' // 指定css的類名格式
}
}
]
}
]
}
};
由此通過 import 引入的css 文件只會作用于該模塊中,而且只有通過先引入再使用才能起作用,相當于把 css 文件看成了模塊。比如:
創建一個Greeter.css文件來進行一下測試
/* Greeter.css */
.root {
background-color: #eee;
padding: 10px;
border: 3px solid #ccc;
}
import React, {Component} from 'react';
import config from './config.json';
import styles from './Greeter.css';//導入一個 Greeter.css 樣式,但是不會自動起作用,更不會影響其他 JS 模塊,只有自動引入再使用才能起作用。
class Greeter extends Component{
render() {
return (
<div className={styles.root}> //使用cssModule添加類名的方法,這里就是使用引入的 css 文件
{config.greetText}
</div>
);
}
}
export default Greeter
如果沒有使用 CSS Module 的話,在模塊中引入 .css 文件,那么該 .css 文件將作用于全部模塊而不僅僅是引入的模塊(相當于直接在html文件的style標簽中引入了樣式,而且類名沒有更改)
4.1.2、css處理平臺PostCSS(使css代碼兼容各種瀏覽器)
使用PostCSS來為CSS代碼自動添加適應不同瀏覽器的CSS前綴。首先安裝postcss-loader 和 autoprefixer(自動添加前綴的插件)
npm install --save-dev postcss-loader autoprefixer
接下來,在webpack配置文件中添加postcss-loader,在根目錄新建postcss.config.js,并添加如下代碼之后,重新使用npm start打包時,你寫的css會自動根據Can i use里的數據添加不同前綴了。
//webpack.config.js
module.exports = {
...
module: {
rules: [
{
test: /(\.jsx|\.js)$/,
use: {
loader: "babel-loader"
},
exclude: /node_modules/
},
{
test: /\.css$/,
use: [
{
loader: "style-loader"
}, {
loader: "css-loader",
options: {
modules: true
}
}, {
loader: "postcss-loader"
}
]
}
]
}
}
// postcss.config.js
module.exports = {
plugins: [
require('autoprefixer')
]
}
5、HtmlWebpackPlugin插件
這個插件的作用是依據一個簡單的index.html模板,生成一個自動引用你所有打包后的JS文件的新index.html。這在每次生成的js文件名稱不同時非常有用(比如如果添加了hash值,每次生成的JS文件名稱都不一樣,如果不使用該模板,那么每次編譯都需要修改html文件中引入的JS文件名稱)。
npm install --save-dev html-webpack-plugin
這個插件將會自動生成一個新的 HTML 文件并自動引入生成的所有的 JS 文件。示例:
在app目錄下,創建一個index.tmpl.html文件模板,這個模板包含title等必須元素,在編譯過程中,插件會依據此模板生成最終的html頁面,會自動添加所依賴的 css, js,favicon等文件,index.tmpl.html中的模板源代碼如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Webpack Sample Project</title>
</head>
<body>
<div id='root'>
</div>
</body>
</html>
更新webpack的配置文件,新建一個build文件夾用來存放最終的輸出文件,使用插件
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: __dirname + "/app/main.js",//已多次提及的唯一入口文件
output: {
path: __dirname + "/build",
filename: "bundle.js"
},
devtool: 'eval-source-map',
devServer: {
contentBase: "./public",//本地服務器所加載的頁面所在的目錄
historyApiFallback: true,//不跳轉
inline: true//實時刷新
},
module: {
rules: [
{
test: /(\.jsx|\.js)$/,
use: {
loader: "babel-loader"
},
exclude: /node_modules/
},
{
test: /\.css$/,
use: [
{
loader: "style-loader"
}, {
loader: "css-loader",
options: {
modules: true
}
}, {
loader: "postcss-loader"
}
]
}
]
},
plugins: [new HtmlWebpackPlugin({
template: __dirname + "/app/index.tmpl.html"http://new 一個這個插件的實例,并傳入相關的參數
})
],
};
再次執行npm start會發現在build文件夾下面生成了bundle.js和index.html。生成的新的 HTML 文件將自動引入所有的資源。

6、使用 Source Maps(定位報錯文件)
如果原文件中代碼出錯,通過打包后在瀏覽器中很難找到對應的出錯的地方,此時的出錯提示并不一定會指出正確的代碼出錯文件名和位置。而Source Maps能解決這個問題。
通過簡單的配置,webpack就可以在打包時為我們生成的source maps,這為我們提供了一種對應編譯文件和源文件的方法,使得編譯后的代碼可讀性更高,也更容易調試。
在webpack的配置文件中配置source maps,只需要配置devtool
module.exports = {
devtool: 'eval-source-map',
entry: __dirname + "/app/main.js",
output: {
path: __dirname + "/public",
filename: "bundle.js"
}
}
devtool 字段有很多選項,網上看到都推薦開發時使用:cheap-module-eval-source-map 字段,生產時使用:cheap-module-source-map。

但是開發時使用cheap-module-eval-source-map 字段報的錯誤是在編譯后的文件中

我覺得這非常有問題啊,cheap-module-eval-source-map 和 eval-source-map 字段在瀏覽器中都不能很好地顯示錯誤的位置,而 cheap-module-source-map 和 source-map 字段就能很好顯示錯誤出現的文件名和行數。
所以我建議使用:cheap-module-source-map
官網建議開發使用:eval-source-map,但是這個有些錯誤在瀏覽器中并不能很好地展示出來,而是要展開才行,錯誤文件在第一行。生產時使用:none
7、構建本地服務器(修改文件后自動編譯)
devserver 能讓瀏覽器監聽你的代碼的修改,并自動刷新顯示修改后的結果。
Webpack提供一個可選的本地開發服務器,該服務器基于node.js構建。它是一個單獨的組件,在webpack中進行配置之前需要單獨安裝它作為項目依賴
npm install --save-dev webpack-dev-server
配置webpack.config.js 文件:
module.exports = {
devtool: 'eval-source-map',
entry: __dirname + "/app/main.js",
output: {
path: __dirname + "/public",
filename: "bundle.js"
},
devServer: {
contentBase: "./public",//本地服務器所加載的頁面所在的目錄
historyApiFallback: true,//不跳轉
inline: true//實時刷新
}
}
在package.json中的scripts對象中添加如下命令,用以開啟本地服務器:
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "webpack",
"server": "webpack-dev-server --open"
}
在終端中輸入npm run server即可在本地的8080端口查看結果。
webpack-dev-server 在編譯之后不會寫入到任何輸出文件。而是將 bundle 文件保留在內存中,然后將它們 serve 到 server 中,就好像它們是掛載在 server 根路徑上的真實文件一樣。也就是說,在啟動webpack-dev-server后,你在目標文件夾中是看不到編譯后的文件的,實時編譯后的文件都保存到了內存當中。因此使用webpack-dev-server進行開發的時候是看不到編譯后的文件。
8、代碼分離(提取公共模塊到單獨的bundle)
8.1、使用SplitChunksPlugin插件
SplitChunksPlugin插件可以將公共的依賴模塊提取到已有的入口 chunk 中,或者提取到一個新生成的 chunk。
該插件可以將node_modules中代碼單獨打包成一個chunk最終輸出。在多入口配置時,該插件會自動分析多入口chunk中有沒有公共的依賴文件,如果有會打包成單獨一個chunk。
比如,index.js 和 another-module.js 都依賴 lodash,配置文件 webpack.config.js 如下:

使用 optimization.splitChunks 配置選項之后,插件將會將 lodash 分離到單獨的 chunk。編譯后的 index.bundle.js 和 another.bundle.js 中已經移除了重復的依賴模塊 lodash。
8.2、使用 CommonChunkPlugin 插件
參考:https://www.webpackjs.com/guides/code-splitting/
如果多個入口文件都共同引入了同一個模塊,正常編譯的話webpack會將依賴的模塊編譯進每個出口文件中,而 CommonsChunkPlugin 插件可以將多個入口文件共同依賴的公共模塊提取到一個新生成的出口文件中(chunk)。
const path = require('path');
const webpack = require('webpack');
const HTMLWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: {
index: './src/index.js',
another: './src/another-module.js'
},
plugins: [
new HTMLWebpackPlugin({
title: 'Code Splitting'
}),
new webpack.optimize.CommonsChunkPlugin({
name: 'common' //指定公共 bundle 的名稱。
})
],
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist')
}
};
如此生成的多個出口文件將減輕了大小。使用 HTMLWebpackPlugin 插件將生成的模塊自動引入 HTML 文件中。
8.2.1、將第三庫的代碼提取到單獨的出口文件中
代碼中引入的第三方依賴庫(例如 lodash 或 react、vue)它們很少像本地的源代碼那樣頻繁修改。因此將它們提取到一個單獨的出口文件中,并且用固定的一個名字命名,這樣瀏覽器可以只用緩存的文件,減少請求。
通過指定 entry 配置中一個未用到的名稱,去重插件會自動將我們指定的第三方庫提取到單獨的包中:
var path = require('path');
const webpack = require('webpack');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: './src/index.js',
entry: {
main: './src/index.js',
vendor: [
'lodash'
]
},
plugins: [
new CleanWebpackPlugin(['dist']),
new HtmlWebpackPlugin({
title: 'Caching'
}),
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor' //指定的第三方庫即上面的 lodash 將自動提取到這里
}),
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest' //其他的共用代碼提取到這里
})
],
output: {
filename: '[name].[chunkhash].js', //這個時候用[chunkhash]才有意義
path: path.resolve(__dirname, 'dist')
}
};
配合 [chunkhash] 使用,當我們修改本地代碼時,由第三方庫提取出的文件的文件名并不會改變,因此瀏覽器將會使用緩存的文件,達到了減少請求的功能。
8.2.2、解決去重代碼出現的問題(模板標識符)
提取出第三庫的文件有可能會因為在模塊中的引用順序發生變化而導致編譯的文件名也發生改變,這顯然不是我們想要的,因為第三庫并沒有改變。
可以使用兩個插件來解決這個問題。第一個插件是 NamedModulesPlugin將使用模塊的路徑,而不是數字標識符。雖然此插件有助于在開發過程中輸出結果的可讀性,然而執行時間會長一些,推薦用于開發環境。第二個選擇是使用 HashedModuleIdsPlugin,推薦用于生產環境構建。
只要在webpack的配置文件中添加代碼:
//只要在plugins 中引入插件: //使用HashedModuleIdsPlugin插件 new webpack.HashedModuleIdsPlugin() //使用NamedModulesPlugin插件 new webpack.NamedModulesPlugin()
8.3、動態導入(import()、require.ensure)
當我們使用動態導入(import().then())來導入一個模塊而不是靜態導入時,webpack會自動將該模塊分離到單獨的 bundle。import() 將會返回一個 promise。
//動態導入語法 import('a.js').then(() => { console.log('導入成功'); }) //靜態導入語法 import {func} from a.js
使用上面動態導入會將模塊自動分離到單獨的 bundle,但是文件命名會根據 id 命名,每次也可能會發生改變。我們可以使用 webpackChunkName 來指定生成的文件名稱:
//動態導入語法 import(/* webpackChunkName: 'aaa' */'a.js').then(() => { console.log('導入成功'); })
上述寫法將會將 a.js 打包至 aaa.js 文件中。
9、懶加載和預加載
9.1、懶加載
使用動態導入語法我們就可以實現懶加載的功能了。
之前使用的一些靜態導入比如:import {func} from a.js ,這種語法在頁面一加載時就會立即加載 a.js 文件。但是當我們使用動態導入時,就可以在一些特定條件時才導入文件,因為動態導入語法不需要放在文件開頭,也不會在編譯階段執行。
比如在某個按鈕被點擊時才調用 a.js 中的某個方法,此時才需要加載 a.js,這時候我們可以這么寫:
btn.onclick = function() { import('a.js').then( ({add}) => { console.log(add(1, 2)); }) }
上述代碼就實現了懶加載,只有當按鈕點擊時即某個事件觸發時,才會加載 a.js 文件,否則將一直不會加載該文件。并且不會重復加載,即只加載一次,再次點擊不會重新加載。
9.2、預加載
預加載會在瀏覽器空閑時加載文件。可以跟懶加載結合使用,就可以在用戶主動觸發或者在瀏覽器空閑時都能加載了。
btn.onclick = function() { import(/* webpackChunkName: 'aaa', webpackPrefetch: true */'a.js').then( ({add}) => { console.log(add(1, 2)); }) }
但是預加載兼容性比較差,在IE和低版本瀏覽器中可能會有問題,所以請慎用。
10、緩存問題(hash)
當我們使用 webpack 來打包代碼,webpack 會生成一個可部署的文件夾(比如:/dist),然后把打包后的內容放置在此目錄中。只要把 /dist 目錄中的內容部署到服務器上,客戶端(通常是瀏覽器)就能夠訪問網站此服務器的網站及其資源。當瀏覽器會使用緩存技術來將文件緩存。當我們在部署新版本時如果不更改資源的文件名,瀏覽器可能會認為它沒有被更新,就會使用它的緩存版本。由于緩存的存在,瀏覽器可能無法獲取到我們部署的新版本。
通過使用 [hash] 來使得每次更改代碼之后也能更改掉文件名,可以確保瀏覽器獲取到修改后的文件。[hash] 替換可以用于在文件名中包含一個構建相關的 hash,但是更好的方式是使用 [chunkhash] 替換,在文件名中包含一個 chunk 相關的哈希。
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
module.exports = {
..
output: {
path: __dirname + "/build",
filename: "[name].hash].js"
},
...
};
[chunkhash] 跟熱替換模塊功能的插件 HotModuleReplacementPlugin 有沖突,要想使用 [chunkhash] 必須把熱替換注釋掉。[chunkhash] 只建議在生產環境中使用。
[hash] 是有一個文件修改那么所有的文件的hash值都修改。

浙公網安備 33010602011771號