前端將大型項(xiàng)目分成一個(gè)個(gè)單獨(dú)的模塊,一般封裝好的每個(gè)模塊都會(huì)實(shí)現(xiàn)一個(gè)目的明確的完成的功能。如何處理這些模塊以及模塊之間的依賴,將這些模塊打包在一起成為一個(gè)完整的應(yīng)用,就是webpack的任務(wù)。webpack一般從entry文件開始,逐步的搜索項(xiàng)目的所有依賴,通過各種loader(比如css-loader、url-loader、babel-loader)以及各種插件(如commonChunksPlugin、extractTextPlugin、UglifyJsWebpackPlugin、HtmlWebpackPlugin、InlineManifestWebpackPlugin、DefinePlugin、hashedModulesPlugin、OptimizeCssAssetsPlugin)處理優(yōu)化資源文件,最終打包出一個(gè)或者多個(gè)適合在瀏覽器中運(yùn)行的js文件
基本知識(shí)
- entry
- output
- externals
- loaders
- plugins
- resolve
- configuration
- modules
- modules resolution
- targets
- manifest
- code split
- Caching
開發(fā)和發(fā)布環(huán)境配置
- 通用配置
- 公共插件
- webpack-merge
- HtmlWebpackPlugin
- DefinePlugin
- 公共插件
- 開發(fā)環(huán)境
- 使用source map
- 配置一個(gè)實(shí)現(xiàn)熱更新的localhost的服務(wù)器
- 插件
- connect-history-api-fallback
- HotModuleReplacementPlugin
- friendly-errors-webpack-plugin
- 發(fā)布環(huán)境
- tree shaking
- source map
- Plugin
- ExtractTextPlugin
- DefinePlugin
- CommonsChunkPlugin
- inline-manifest-webpack-plugin
- HashedModuleIdsPlugin
- optimize-css-assets-webpack-plugin
- ModuleConcatenationPlugin
css loaders
基本知識(shí)
entry
entry: { [entryChunkName:string]: string | Array(string) }
,配置入口文件的地址,可以是string或者string組成的array
entry: {
pageOne: './src/pageOne/index.js',
pageTwo: './src/pageTwo/index.js',
pageThree: './src/pageThree/index.js'
}
output
output: {
filename: string // 打包輸出的文件名
path: string // 打包輸出文件的路徑
chunkFilename: string // 代碼分離時(shí)打包輸出的塊的名字
publicPath: string // 配置資源的CDN或者h(yuǎn)ash地址
}
externals
防止將某些 import 的包(package)打包到 bundle 中,而是在運(yùn)行時(shí)(runtime)再去從外部獲取這些擴(kuò)展依賴(external dependencies)。
<script src="https://code.jquery.com/jquery-3.1.0.js"
integrity="sha256-slogkvB1K3VOkzAI8QITxV3VzpOnkeNVsKvtkYLMjfk="
crossorigin="anonymous"></script>
externals : {
jquery: 'jQuery'
}
import $ from 'jquery'
就會(huì)在運(yùn)行時(shí)去獲取通過 <script src="...">
引入的jquery
externals : {
lodash : {
commonjs: "lodash",
amd: "lodash",
root: "_" // 指定全局變量
}
}
// or
externals : {
subtract : {
root: ["math", "subtract"] //轉(zhuǎn)換為父子結(jié)構(gòu),其中 math 是父模塊,而 bundle 只引用 subtract 變量下的子集。
}
}
通過window['math']['subtract']
訪問subtract
loaders
loaders是模塊源碼的轉(zhuǎn)換器。可以將一些其他類型的語言(如typescript)等轉(zhuǎn)換為javascript,內(nèi)聯(lián)圖片和一些其他的資源,loaders甚至允許你在js中import css資源
module: {
rules: [
{
test: /\.css$/,
use: [
{ loader: 'style-loader' },
{
loader: 'css-loader',
options: {
modules: true
}
}
]
}
]
}
在module.rules定義一些列l(wèi)oaders
- loaders可以被串聯(lián)在一起。如
sass: 'vue-style-loader!css-loader?sourceMap!sass-loader?indentedSyntax&sourceMap'
- loaders可以是同步或者異步的
- loaders運(yùn)行在Nodejs中,所以可以使用Nodejs的資源
- loaders可以接受query參數(shù)
- loaders也可以配置options參數(shù)
- 插件可以給loaders提供更多特性
使用方法
loader的options:: 修改文件名,放在輸出目錄下,并返其對應(yīng)的 url
test: 檢測哪些文件需要此loader,是一個(gè)正則表達(dá)式
exclude: 忽略哪些文件
file-loader
處理資源文件,尋找資源文件并將其放在輸出目錄里面,為了更好地緩存文件,可以使用版本hash為資源文件命名。
require("file?name=html-[hash:6].html!./page.html");
// => html-109fa8.html
資源路徑規(guī)則:
- 相對URLs:如
./assets/logo.png
會(huì)解釋成require('./assets/logo.png')
- 沒有前綴URLs:如
assets/logo.png
會(huì)被當(dāng)做相對路徑./assets/logo.png
-
~
為前綴:如~assets/logo.png
解釋成require('assets/logo.png')
- 絕對路徑: 不會(huì)被處理
url-loader
如果圖片大小小于一個(gè)limit,轉(zhuǎn)化為base64,否則回退到file-loader的功能
html-loader
把Html文件輸出成字符串。它默認(rèn)處理html中的[圖片上傳失敗...(image-58f768-1513065689939)]
為require("./image.png")
,同時(shí)需要在你的配置中指定image文件的加載器,比如:url-loader或者file-loader。
css-loader、postcss-loader、style-loader
postcss-loader:對css應(yīng)用autoprefixer
css-loader:解決css文件中的路徑問題,將css中資源作為依賴:如background: url(./logo.png)
,webpack以獲取模塊的方式來獲取圖片
style-loader:將css轉(zhuǎn)化為js模塊,注入 <style>標(biāo)簽
loader: 'style!css!postcss'
sass-loader
將sass解析成css,需要node-sass、webpack作為依賴。query parameters:https://github.com/sass/node-sass
Plugins
plugins是webpack的“骨”,webpack自己也是建立在plugins之上的。plugins可以做一些loaders做不了的工作
plugins: [
new webpack.optimize.UglifyJsPlugin(), // 丑化代碼
new HtmlWebpackPlugin({template: './src/index.html'}) // 提供html模板
]
reslove
resolve.modules
在resolve模塊的時(shí)候,告訴webpack去哪些文件夾搜索
modules: [
resolve('src'),
resolve('node_modules')
]
如果src文件夾下面有個(gè)a文件,那么就可以直接 require('a'),webpack就會(huì)去src文件夾下面尋找a。
會(huì)順著引用依賴的文件目錄逐級向上尋找,直至找到目標(biāo)文件。例如 文件夾目錄為
那么如果在
main.js
中import 'a'
會(huì)逐級向上搜索,在第一個(gè)src
下面找到a
文件就會(huì)返回這個(gè)文件對象,停止搜索
resolve.alias
alias: {
vue$: 'vue/dist/vue.common.js',
Utilities: path.resolve(__dirname, 'src/utilities/')
}
那么import 'src/utilities/a.js'
就可以簡寫成import 'Utilities/a.js'
加一個(gè)$
用于精確匹配路徑。
resolve.extensions
用于搜索指定的擴(kuò)展名
extensions: [".js", ".json", ".vue", ".ts"]
resolve.mainFields
當(dāng)引入一個(gè)package.json中的包時(shí),mianField字段決定了check這個(gè)包的package.json的哪個(gè)字段
當(dāng)target設(shè)置為web、webworker或者未指定時(shí),mainFields: ["browser", "module", "main"]
否則mainFields: ["module", "main"]
例如import * as D3 from "d3"
, D3包的package.json
{
...
main: 'build/d3.Node.js',
browser: 'build/d3.js',
module: 'index',
...
}
就會(huì)從browser
字段對應(yīng)的地址(此處是build/d3.js
)去尋找文件(按照mainFileds定義的先后順序?qū)ふ椅募?
resolve.mainFiles
當(dāng)查找到的是一個(gè)文件目錄,那么mainFiles決定了這個(gè)目錄下查找的文件名
mainFiles: ["main"]
那么解決到文件目錄時(shí),返回main名字的文件
configuration、modules、modules resolution
configuration
webpack的配置文件就是導(dǎo)出對象的js文件。因?yàn)樗菢?biāo)準(zhǔn)的Nodejs Commonjs模塊,所以可以
- 通過require(...)引入文件
- 使用js的控制流表達(dá)式,如
?:
- 使用constants(常量)和variables(變量)
- 編寫和使用函數(shù)
webpack接受多種語言編寫的配置文件。支持的文件后綴可以在node-interpret中找到。webpack能夠處理Typescript、coffeeScript、Babel和JSX等編寫的配置文件
除了導(dǎo)出一個(gè)對象文件,webpack還可以導(dǎo)出一個(gè)函數(shù)、一個(gè)promise和一個(gè)數(shù)組(數(shù)組里面是多種配置)
modules
在編程時(shí),開發(fā)人員將程序分割成多個(gè)離散的成為功能的模塊。編寫良好的模塊具有封裝性和抽象性。應(yīng)用程序中的每個(gè)模塊都有一致的設(shè)計(jì)和清晰的目的,如驗(yàn)證、調(diào)試和測試
webpack的模塊能夠可以通過以下方式聲明他們的依賴
-
ES2015的
import
聲明 -
CommonJS的
require
聲明 -
AMD 的
define
和require
聲明 - 在css/sass/less 文件@import 聲明
- 樣式表 (url(...)) 或者h(yuǎn)tml (<img src=...>) 文件中的圖片url
通過loaders,webpack支持一系列的語言和預(yù)處理器。loaders告訴webpack如何處理非js模塊以及將這些模塊打包到bundles里面。
modules Resolution
一個(gè)文件可以作為另外一個(gè)文件的依賴,reslover就是定位依賴文件(找到文件的絕對路徑)的解決方案。
絕對路徑
import "/home/me/file";
import "C:\\Users\\me\\file";
相對路徑
找到require
和import
發(fā)生的上下文目錄。上下文目錄的路徑和相對路徑拼接形成文件的絕對路徑
modlue 路徑
將搜索定義在 resolve.modules中的所有目錄。也可以通過resolve.alias設(shè)置文件目錄的別名
- 如果最后resolve的路徑是一個(gè)文件:
如果路徑帶有文件名后綴,那么久定位到了文件。否則,定位以resolve.extensions選項(xiàng)中的后綴名為后綴的文件 - 如果最后resolve路徑是一個(gè)目錄
- 如果目錄下包含package.json,按照resolve.mainFields的options的先后順序查找文件
- 如果沒有package.json字段或者mainFields沒有返回有效的路徑,就會(huì)按照先后順序在resolve.mainFiles中查找
- extension也是在
resolve.extensions
選項(xiàng)中找
resolve loaders
方法和查找文件的方法一致。只是使用resolveLoader
Targets
由于js可以用在瀏覽器和服務(wù)器端。webpack提供了target,例如
module.exports = {
target: 'node'
};
webpack會(huì)將源代碼編譯成適用于Node.js類似環(huán)境運(yùn)行的目標(biāo)代碼,
由于不能target不能是一個(gè)array,所以可以通過以下形式配置多個(gè)target環(huán)境
var path = require('path');
var serverConfig = {
target: 'node',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'lib.node.js'
}
//…
};
var clientConfig = {
target: 'web', // <=== can be omitted as default is 'web'
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'lib.js'
}
//…
};
module.exports = [ serverConfig, clientConfig ];
manifest
webpack打包的應(yīng)用主要包括三部分代碼
- 你或者你的團(tuán)隊(duì)成員編寫的源代碼
- 第三方庫或者你的代碼依賴的“vendor”
- 指揮模塊間交互的runtime或者manifest
runtime
runtime的代碼是在瀏覽器中運(yùn)行的,主要包括loading和resolving邏輯。用于連接模塊,控制模塊間的交互
manifest
應(yīng)用程序編譯的時(shí)候,會(huì)保存一份關(guān)于所有模塊的詳細(xì)記錄,這就是manifest,通過manifest,runtime會(huì)知道去哪里提取模塊
在實(shí)際應(yīng)用中,會(huì)使用CommonsChunkPlugin
打包公共模塊
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks: function(module) {
if(module.resource && (/^.*\.(css|scss)$/).test(module.resource)) {
return false;
}
return module.context && module.context.indexOf("node_modules") !== -1;
}
})
打包出來的vendor文件會(huì)包含應(yīng)用程序文件的chunkId和chunkHash等信息
如
entry: {
main: './commonJs/c',
main1: './commonJs/c1',
main2: './commonJs/c2',
main3: './commonJs/c3',
main4: './commonJs/c4',
}, // 一般entry文件只有一個(gè),這里僅是一個(gè)說明例子
output: {
path: \_\_dirname + '/dist',
filename: "[name].[chunkhash:7].js"
}
打包出來的vendor會(huì)包含以下一段代碼
var head = document.getElementsByTagName('head')[0];
script.src = __webpack_require.p + "" + chunkId + "." + ({"0":"main","1":"main1","2":"main2","3":"main3","4":"main4","5":"commons"}[chunkId]||chunkId) + "." + {"0":"0c9ed2","1":"9bef64","2":"dfadbc","3":"21a5a9","4":"d8fc6e","5":"7a6ed2"}[chunkId] + ".js";
head.appendChild(script);`
那么入口任意一個(gè)文件有些許變化,都會(huì)導(dǎo)致chunkId或者chunkHash的變化,從而導(dǎo)致整個(gè)vendor文件的重新加載,解決方法就是將這段代碼提取出來,這段提取出來的代碼即manifest
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest'
})
code split、懶加載
有時(shí)候打包出來的模塊太大,可能會(huì)影響模塊的加載速度,而有些代碼不必馬上加載,此時(shí)可以通過import實(shí)現(xiàn)代碼分離
import(
/* webpackChunkName: "my-chunk-name" */
/* webpackMode: "lazy" */
'lodash'
);
lodash
將會(huì)另外打包成一個(gè)js文件,而不會(huì)打包至當(dāng)前的js文件中。
webpackChunkName: 打包輸出的文件名,
webpackChunkName
對應(yīng)webpack配置中output: { chunkFilename: '[name].[chunkhash:6].js' }
里面的namewebpackMode:
-
"lazy"
(default): 對每一個(gè)import()
的模塊創(chuàng)建一個(gè)可以懶加載的chunk -
"lazy-once"
: 對所有的import()
模塊創(chuàng)建一個(gè)可以懶加載的chunk -
"eager"
: 不創(chuàng)建額外的chunk,所以也不會(huì)有額外的網(wǎng)絡(luò)請求。對比靜態(tài)import
,import
返回一個(gè)resolved的promise,在返回resolved的promise,模塊不會(huì)執(zhí)行 "weak"
以下方法可以將a,b模塊打包到一個(gè)chunk中
() => import(/* webpackChunkName: "a" */ './a')
() => import(/* webpackChunkName: "a" */ './b')
注意:
- 如果
.babelrc
的配置中"comments": false
, 注釋會(huì)失效,webpackChunkName
,webpackMode
也就失效了 -
.babelrc
配置需要引入"syntax-dynamic-import"
,即plugins: ["syntax-dynamic-import"]
Caching
通過webpack最后輸出了一個(gè)可以部署在服務(wù)器中的dist
目錄。瀏覽器從服務(wù)器獲取網(wǎng)站和資源,資源的獲取可能會(huì)耗時(shí),所以瀏覽器提供了caching技術(shù),即資源沒有改變時(shí),瀏覽器就會(huì)使用緩存,不會(huì)重新去服務(wù)器獲取新的代碼
webpack通過根據(jù)文件內(nèi)容生成hash,內(nèi)容變化導(dǎo)致hash變化,進(jìn)而重新去服務(wù)器請求新的code。
output: {
filename: "[name].[chunkhash:6].js", // 配置入口打包輸出的hash
chunkFilename: 'js/[name].[chunkhash:6].js' // 配置chunk的hash
},
module: {
rules: [
{
test: /\.(png|jpg|gif|svg)$/,
use: [
{
loader: 'url-loader',
options: {
limit: 10000,
name: 'assets/img/[name].[hash:7].[ext]' // 配置圖片資源的hash
}
}
]
}
]
}
plugins: [
new extractTextPlugin({
filename: "[name].[contenthash:6].css", // 提取出來的css文件的hash
allChunks: true // 有分離文件的樣式也會(huì)全部壓縮到一個(gè)css文件上
})
]
開發(fā)和發(fā)布環(huán)境配置
發(fā)布環(huán)境的配置和開發(fā)環(huán)境不一樣。在開發(fā)環(huán)境我們需要配置一個(gè)支持刷新或者熱更新的localhost的服務(wù)器,需要source map。在發(fā)布環(huán)境,我們關(guān)注于如何縮小輸出的bundle的體積,使用輕便的source map,優(yōu)化資源來提升加載速度。通常建議對不同的環(huán)境寫一個(gè)不同的配置。
對于兩個(gè)環(huán)境,會(huì)有一些相同的配置,比如,入口文件,輸出文件地址等。我們一般提取出相同的配置至webpack.common.js
中,
通用配置
了解了基礎(chǔ)配置以后,可以開始配置部分開發(fā)環(huán)境和發(fā)布環(huán)境的配置。
首先,我們有一個(gè)webpack.common.js
,這個(gè)文件包含開發(fā)環(huán)境和測試環(huán)境的公共配置
const path = require('path');
//一個(gè)公共方法,用于resolve文件
function resolve (dir) {
return path.join(__dirname, '..', dir)
}
module.exports = {
entry: {
app: [resolve('./src/main.js')]
},
output: {
filename: '[name].js',
path: resolve('dist'),
publicPath: '/'
},
resolve: {
extensions: ['.js'],
modules: [
resolve('src'),
resolve('node_modules')
],
alias: {
'src': resolve('src')
}
},
module: {
rules: [
{
test: /\.js$/,
loader: 'babel-loader',
include: resolve('src')
},
{
test: /\.(png|jpg|gif|svg)$/,
use: [
{
loader: 'url-loader',
options: {
limit: 10000,
name: 'assets/img/[name].[hash:7].[ext]'
}
}
]
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/,
use: [
{
loader: 'url-loader',
options: {
limit: 10000,
name: 'assets/img/[name].[hash:7].[ext]'
}
}
]
}
]
}
}
babel-loader
: 對es6代碼做一些處理,轉(zhuǎn)化成適合在瀏覽器中運(yùn)行的js代碼。
url-loader
: 如果圖片大小小于一個(gè)limit,轉(zhuǎn)化為base64,否則回退到file-loader的功能。所以再使用url-loader
時(shí)候,要按照file-loader
依賴
公共插件
webpack-merge
現(xiàn)在有一個(gè)公共的webpack配置webpack.common.js
,在特定的development或者production環(huán)境的webpack配置中,我們需要合并公共配置,就可以使用webpack-merge
var commonConfig = require('./webpack.common.js')
const merge = require('webpack-merge')
const config = merge(commonConfig, {
module: {
rules: [
...
]
},
devtool: '#source-map',
plugins: [
...
]
})
HtmlWebpackPlugin
此插件用于生產(chǎn)一個(gè)html文件。可以用HtmlWebpackPlugin
直接生成html文件,也可以根據(jù)你提供的html文件,根據(jù)HtmlWebpackPlugin
生成一個(gè)html文件。將這個(gè)生成的html文件打包到輸出中。
基本用法:
var HtmlWebpackPlugin = require('html-webpack-plugin');
var path = require('path');
var webpackConfig = {
entry: 'index.js',
output: {
path: path.resolve(__dirname, './dist'),
filename: 'index_bundle.js'
},
plugins: [new HtmlWebpackPlugin()]
};
會(huì)生成一個(gè)包含以下內(nèi)容的dist/index.html文件
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>webpack App</title>
</head>
<body>
<script src="index_bundle.js"></script>
</body>
HtmlWebpackPlugin
的配置項(xiàng)
template
:html5模板地址,根據(jù)該template生成html
inject
:true | 'head' | 'body' | false,inject
為true或 'body',所有的js文件會(huì)注入body元素的最后面。inject
為head時(shí),js會(huì)注入head中
filename
:生成的html文件名
title
: html文件title
chunks
:只添加指定的chunks
excludeChunks
:指定不添加某些chunks
chunksSortMode
:控制chunk的排序,有'none' | 'auto' | 'dependency' | {function} ,默認(rèn)auto
DefinePlugin
創(chuàng)建全局的變量
new webpack.DefinePlugin({
DEV: JSON.stringify(true)
PRODUCTION: JSON.stringify(false)
})
如在只在測試環(huán)境下打印出一些log的時(shí)候,就會(huì)很有用,如:
DEV && (console.log('這是一個(gè)只會(huì)在測試環(huán)境下打印出的信息'))
開發(fā)環(huán)境
打造一個(gè)應(yīng)用的開發(fā)環(huán)境。在開發(fā)環(huán)境我們需要source map,需要配置一個(gè)支持刷新或者熱更新的localhost的服務(wù)器,
使用source map
由于瀏覽器中運(yùn)行的代碼都是打包輸出的bundle,當(dāng)你的代碼出現(xiàn)錯(cuò)誤或者警告的時(shí)候,可以通過source map定位到出錯(cuò)的源代碼。此處以inline-source-map
為例(更多devtool選擇)
devtool: 'inline-source-map'
配置一個(gè)實(shí)現(xiàn)熱更新的localhost的服務(wù)器
- webpack-dev-server:一個(gè)簡單的web服務(wù)器。使用webpack-dev-server后,任意修改一個(gè)文件,保存后,編譯完成后刷新瀏覽器,就能看到更新。webpack-dev-server會(huì)把編譯后的文件存放到內(nèi)存里面,而不是輸出到文件目錄。
- webpack-dev-middleware:webpack-dev-middleware就是一個(gè)中間件,將webpack的修改發(fā)射給服務(wù)器。webpack-dev-server的內(nèi)部也使用了webpack-dev-middleware
- webpack-hot-middleware:結(jié)合webpack-hot-middleware使用,實(shí)現(xiàn)無刷新更新(hot reload)
express可以用于構(gòu)建一個(gè)web服務(wù)器,webpack-dev-middleware和webpack-hot-middleware都是適用于express的中間件,可以通過express構(gòu)建一個(gè)可以啟動(dòng)一個(gè)適用于熱更新的應(yīng)用
development.js
const app = require('express')()
const webpack = require('webpack')
const config = require('./webpack.dev.js') // 獲取測試環(huán)境的webpack配置
const compiler = webpack(config)
const port = 3000
//告訴webpack-dev-middleware去使用webpack配置
const devMiddle = require('webpack-dev-middleware')(compiler, {
quiet: true,
publicPath: config.output.publicPath
})
//告訴webpack-hot-middleware去使用webpack配置
const hotMiddle = require('webpack-hot-middleware')(compiler, {
log: () => {}
})
//告訴express服務(wù)器去使用中間件
app.use(devMiddle)
app.use(hotMiddle)
app.listen(port, function (err) {
if (err) {
console.log(err)
} else {
console.log('Listening at http://localhost:' + port + '\n')
}
})
在package.json
中,通過"scripts": { "start": "node development.js", },
就可以啟動(dòng)一個(gè)localhost服務(wù)
更多參考Express結(jié)合Webpack的全棧自動(dòng)刷新
然后在每個(gè)entry后面增加一個(gè)hotMiddlewareScript,告訴瀏覽器在不能hot reload的時(shí)候,整頁刷新
var hotMiddlewareScript = 'webpack-hot-middleware/client?reload=true'
module.exports = {
entry: {
app: ['./src/main.js', hotMiddlewareScript]
}
}
完成上述配置后,包含HMR(熱更新)的模塊在代碼被修改后就可以實(shí)現(xiàn)熱更新。通常會(huì)全局開啟代碼熱替換,可以通過插件webpack. HotModuleReplacementPlugin()
實(shí)現(xiàn)
plugins: [
new webpack. HotModuleReplacementPlugin()
]
適用于開發(fā)環(huán)境的一些其他插件
connect-history-api-fallback
在使用h5 history的api的時(shí)候,獲取不到index.html
的頁面就會(huì)返回404
錯(cuò)誤,這時(shí)候就需要使用connect-history-api-fallback
,對其他頁面的請求也發(fā)送index.html
給瀏覽器端
app.use(require('connect-history-api-fallback')())
app.use(devMiddleware)
app.use(hotMiddleware)
注意:connect-history-api-fallback
中間件需要在devMiddleware
之前使用
friendly-errors-webpack-plugin
友好的輸出錯(cuò)誤提示
plugins: [ new FriendlyErrorsPlugin({}) ]
開發(fā)環(huán)境webpack.dev.js
的最終配置
const commonConfig = require('./webpack.common.js')
const merge = require('webpack-merge')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
const webpack = require('webpack')
var hotMiddlewareScript = ['webpack-hot-middleware/client?noInfo=true&reload=true']
commonConfig.entry.app = commonConfig.entry.app.concat(hotMiddlewareScript)
var outputConfig = merge(commonConfig, {
module: {
rules: [
// 對css處理的loader
{
test: /\.css$/,
use: [
{
loader: 'style-loader'
}
{
loader: 'css-loader'
option: {
sourceMap: true
}
}
]
}
]
},
devtool: '#source-map',
plugins: [
new webpack.HotModuleReplacementPlugin(),
new webpack.DefinePlugin({
__DEV__: true,
__PRODUCTION__: false
}),
new HtmlWebpackPlugin({
filename: 'index.html',
template: 'src/index.dev.html',
inject: true
}),
new FriendlyErrorsPlugin({})
]
})
module.exports = outputConfig
發(fā)布環(huán)境
在發(fā)布環(huán)境,我們關(guān)注于如何縮小輸出的bundle的體積,使用輕便的source map,優(yōu)化資源來提升加載速度
tree shaking(UglifyJSPlugin)
export function square(x) {
return x * x;
}
export function cube(x) {
return x * x * x;
}
上面的代碼,如果你只用到了cube
函數(shù),打包后會(huì)發(fā)現(xiàn)square
函數(shù)也在輸出的bundle里面
把你的代碼想象成一棵樹,綠色的葉子代表有用的代碼(如上述的cube),棕色的死了的葉子代表沒有用到的代碼(如上述的square),那么你用勁搖晃這棵樹,希望將死掉的葉子搖晃下來(在output的bundle中剔除無用的代碼)。這就是tree shaking
const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
module.exports = {
plugins: [new UglifyJSPlugin(
compress: {
warnings: false
}
)]
}
uglifyjs-webpack-plugin
還會(huì)對代碼進(jìn)行壓縮丑化,會(huì)大大減小打包輸出文件的體積。
要實(shí)現(xiàn)tree shaking的效果需要注意:
- 要實(shí)現(xiàn)es6模塊語法(如:
import
和export
) - 能夠丑化代碼并且支持無用代碼移除的插件(如UglifyJSPlugin)
source map
在運(yùn)行基準(zhǔn)測試的時(shí)候,source map是非常有用的,因此,建議在發(fā)布環(huán)境也配置source map,建議配置
module.exports = merge(common, {
devtool: 'source-map', // 使用'source-map'選項(xiàng)
plugins: [
new UglifyJSPlugin({
sourceMap: true
})
]
})
ExtractTextPlugin
在打包輸出的文件中,css也會(huì)被打包至js文件中。我們需要把這一部分css文件從js文件中提取至一個(gè)單獨(dú)的css文件中。就需要用到ExtractTextPlugin
,使用方法
const ExtractTextPlugin = require('extract-text-webpack-plugin')
module: {
rules: [
{
test: /\.css$/,
use: ExtractTextPlugin.extract({
use: 'css-loader',
fallback: 'vue-style-loader'
})
},
{
test: /\.css$/,
use: ExtractTextPlugin.extract({
use: 'css-loader',
fallback: 'style-loader'
})
}
]
}
plugins: [
new ExtractTextPlugin({
filename: '[name].[contenthash:6].css',
allChunks: true
})
]
new ExtractTextPlugin(option)
這個(gè)option是一個(gè)對象,包含一個(gè)filename屬性,表示打包輸出的css文件名。
'[name].[contenthash:6].css'
,其中name
表示打包輸出的文件名,contenthash
表示根據(jù)文件內(nèi)容生成的哈希,contenthash:6
表示長度為6
ExtractTextPlugin.extract(options: loader | object)
options.use
:string | Array | object。表示把資源文件轉(zhuǎn)化為css文件需要用到的loaders
options.fallback
: css沒有提取到的時(shí)候使用的loader
關(guān)于更多l(xiāng)oaders的處理
module: {
rules: [
{
test: /\.css$/,
use: extractTextPlugin.extract({
use: [
{
loader: 'css-loader',
options: {
sourceMap: false
}
}
],
fallback: 'style-loader'
})
},
{
test: /\.(sass|scss)$/,
use: extractTextPlugin.extract({
use: [
{
loader: 'css-loader',
options: {
sourceMap: false
}
},
{
loader: 'sass-loader',
options: {
sourceMap: false
}
}
],
fallback: 'style-loader'
})
},
{
test: /\.vue$/,
use: {
loader: 'vue-loader',
options: {
loaders: {
css: extractTextPlugin.extract({
use: 'css-loader',
fallback: 'vue-style-loader'
}),
sass: extractTextPlugin.extract({
use: 'css-loader!sass-loader?indentedSyntax',
fallback: 'vue-style-loader'
}),
stylus: extractTextPlugin.extract({
use: 'css-loader!stylus-loader',
fallback: 'vue-style-loader'
}),
styl: extractTextPlugin.extract({
use: 'css-loader!stylus-loader',
fallback: 'vue-style-loader'
}),
}
}
}
}
]
}
extract-text-webpack-plugin的更多用法
DefinePlugin
定義一些常量,用于判斷環(huán)境后執(zhí)行一些特性環(huán)境下執(zhí)行的logging或者testing,易于測試
plugins: [
new webpack.DefinePlugin({
DEV: JSON.stringify(false),
PRODUCTION: JSON.stringify(true)
})
]
或者
plugins: [
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('production')
})
]
CommonsChunkPlugin
提取多個(gè)入口的公共代碼
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks: function(module) {
// This prevents stylesheet resources with the .css or .scss extension
// from being moved from their original chunk to the vendor chunk
if(module.resource && (/^.*\.(css|scss)$/).test(module.resource)) {
return false;
}
return module.context && module.context.indexOf("node_modules") !== -1;
}
}),
new webpack.optimize.CommonsChunkPlugin({
// 將vendor中的運(yùn)行時(shí)(Runtime)提取至manifest中
name: 'manifest'
})
提取出來的manifest文件非常的小(只有1000多字節(jié)),可以將manifest文件內(nèi)聯(lián)至index.html
中,節(jié)省http請求,這時(shí)候就可以用到inline-manifest-webpack-plugin
插件
inline-manifest-webpack-plugin
const InlineManifestWebpackPlugin = require('inline-manifest-webpack-plugin')
plugins: [ new InlineManifestWebpackPlugin() ]
然后在html的body中添加<%=htmlWebpackPlugin.files.webpackManifest%>
即可
<body>
<%=htmlWebpackPlugin.files.webpackManifest%>
</body>
執(zhí)行后,打開dist中的index.html
文件,可以看到?jīng)]有引入manifest
文件,而是有一段內(nèi)聯(lián)的manifest
代碼
__ HashedModuleIdsPlugin__
比如,我們使用code split引入a, b, c, d四個(gè)文件,打開dist中的vendor文件可以看到
有一段
webpackJsonp([5]
表示vendor自身的chunkId
之后進(jìn)行修改,只引入a,c,d三個(gè)文件。再打開dist/vendor.js
,可以看到webpackJsonp([5]
變成了webpackJsonp([4]
可以看出a,c,d三個(gè)文件以及vendor文件可能會(huì)因?yàn)樽陨韈hunkId的改變而變化,導(dǎo)致瀏覽器重新加載,使用HashedModuleIdsPlugin可以避免該問題
plugins: [ new webpack.HashedModuleIdsPlugin() ]
optimize-css-assets-webpack-plugin
對提取出來的css文件做優(yōu)化,可以比較使用optimize-css-assets-webpack-plugin
前后的css文件體積,會(huì)縮小很多
const OptimizeCssAssetsPlugin = require("optimize-css-assets-webpack-plugin")
plugins: [ new OptimizeCssAssetsPlugin() ]
__ ModuleConcatenationPlugin__
將一些有聯(lián)系的模塊,放到一個(gè)閉包函數(shù)里面去,通過減少閉包函數(shù)數(shù)量從而加快JS的執(zhí)行速度。
更多解釋
最后webpack.production.js
配置
var commonConfig = require('./webpack.common.js')
const merge = require('webpack-merge')
const extractTextPlugin = require('extract-text-webpack-plugin')
const uglifyjsWebpackPlugin = require('uglifyjs-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const webpack = require('webpack')
const InlineManifestWebpackPlugin = require('inline-manifest-webpack-plugin')
const OptimizeCssAssetsPlugin = require("optimize-css-assets-webpack-plugin")
module.exports = merge(commonConfig, {
output: {
filename: "[name].[chunkhash:6].js",
chunkFilename: 'js/[name].[chunkhash:6].js',
publicPath: '/' // 此處根據(jù)實(shí)際情況填寫資源存放的publicPath地址
},
plugins: [
new extractTextPlugin({
filename: "[name].[contenthash:6].css",
allChunks: true
}),
new webpack.DefinePlugin({
DEV: false,
PRODUCTION: true,
'process.env.NODE_ENV': '"production"'
}),
new uglifyjsWebpackPlugin({
compress: {
warnings: false
},
sourceMap: false
}),
new webpack.HashedModuleIdsPlugin(),
new OptimizeCssAssetsPlugin(),
new webpack.optimize.ModuleConcatenationPlugin(),
new HtmlWebpackPlugin({
filename: 'index.html',
template: 'src/index.html',
inject: true,
minify: {
removeComments: true,
collapseWhitespace: true,
removeAttributeQuotes: true
},
// 保證vendor在app前加載
chunksSortMode: 'dependency'
}),
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks: function(module) {
if(module.resource && (/^.*\.(css|scss)$/).test(module.resource)) {
return false;
}
return module.context && module.context.indexOf("node_modules") !== -1;
}
}),
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest'
}),
new InlineManifestWebpackPlugin()
],
devtool: '#source-map'
})
css loaders
建議使用vue-cli封裝的utils.js
,已經(jīng)對當(dāng)前流行的各種類型的css進(jìn)行了封裝處理
例如utils.js
'use strict'
const path = require('path')
const ExtractTextPlugin = require('extract-text-webpack-plugin')
exports.cssLoaders = function (options) {
options = options || {}
const cssLoader = {
loader: 'css-loader',
options: {
sourceMap: options.sourceMap
}
}
const postcssLoader = {
loader: 'postcss-loader',
options: {
sourceMap: options.sourceMap
}
}
// generate loader string to be used with extract text plugin
function generateLoaders (loader, loaderOptions) {
const loaders = options.usePostCSS ? [cssLoader, postcssLoader] : [cssLoader]
if (loader) {
loaders.push({
loader: loader + '-loader',
options: Object.assign({}, loaderOptions, {
sourceMap: options.sourceMap
})
})
}
// Extract CSS when that option is specified
// (which is the case during production build)
if (options.extract) {
return ExtractTextPlugin.extract({
use: loaders,
fallback: 'vue-style-loader'
})
} else {
return ['vue-style-loader'].concat(loaders)
}
}
// https://vue-loader.vuejs.org/en/configurations/extract-css.html
return {
css: generateLoaders(),
postcss: generateLoaders(),
less: generateLoaders('less'),
sass: generateLoaders('sass', { indentedSyntax: true }),
scss: generateLoaders('sass'),
stylus: generateLoaders('stylus'),
styl: generateLoaders('stylus')
}
}
// Generate loaders for standalone style files (outside of .vue)
exports.styleLoaders = function (options) {
const output = []
const loaders = exports.cssLoaders(options)
for (const extension in loaders) {
const loader = loaders[extension]
output.push({
test: new RegExp('\\.' + extension + '$'),
use: loader
})
}
return output
}
vue-loader.conf.js
'use strict'
const utils = require('./utils')
const isProduction = process.env.NODE_ENV === 'production'
const sourceMapEnabled = isProduction
const cacheBustingEnabled = !isProduction
const loaders = {
loaders: utils.cssLoaders({
sourceMap: sourceMapEnabled,
extract: isProduction
}),
cssSourceMap: sourceMapEnabled,
cacheBusting: cacheBustingEnabled,
transformToRequire: {
video: ['src', 'poster'],
source: 'src',
img: 'src',
image: 'xlink:href'
}
}
module.exports = loaders
在webpack.common.js
中,只需要
// 對.vue文件中引入的樣式做處理
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader',
options: require('./vue-loader.conf')
}
}
在webpack.dev.js
中,添加代碼
merge(commonConfig, {
module: {
rules: utils.styleLoaders({
sourceMap: false,
extract: false,
usePostCSS: true
})
}
})
在webpack.production.js
中,添加代碼
merge(commonConfig, {
module: {
rules: utils.styleLoaders({
sourceMap: true,
extract: true,
usePostCSS: true
})
}
})
其他的plugins
CopyWebpackPlugin
用于拷貝文件
portscanner
var portscanner = require('portscanner')
// Checks the status of a single port
portscanner.checkPortStatus(3000, '127.0.0.1', function(error, status) {
// Status is 'open' if currently in use or 'closed' if available
console.log(status)
})
// Find the first available port. Asynchronously checks, so first port
// determined as available is returned.
portscanner.findAPortNotInUse(3000, 3010, '127.0.0.1', function(error, port) {
console.log('AVAILABLE PORT AT: ' + port)
})
// Find the first port in use or blocked. Asynchronously checks, so first port
// to respond is returned.
portscanner.findAPortInUse(3000, 3010, '127.0.0.1', function(error, port) {
console.log('PORT IN USE AT: ' + port)
})
check-dependencies
require('check-dependencies')(config, callback);
或者
require('check-dependencies')(config)
.then(function (output) {
/* handle output */
});
callback = {
??status: number, // 0 if successful, 1 otherwise
??depsWereOk: boolean, // true if dependencies were already satisfied
??log: array, // array of logged messages
??error: array, // array of logged errors
}
config = {
??packageManager: string // 'npm'(default) 或者'bower'
??packageDir: package.json或者bower.json目錄
?? install:安裝缺失的package,默認(rèn)false
??scopeList:去哪些keys尋找package的名字和版本,默認(rèn)['dependencies', 'devDependencies']
??verbose:打印message
??log:打印dubug信息的function,verbose必須為true
??error:打印error信息的function,verbose必須為true
}
webpack中的資源優(yōu)化
1 url-loader:將小的圖片轉(zhuǎn)為base64,內(nèi)聯(lián)在html中,減少資源的http請求
2 將一些不常改變的公共模塊通過commonChunksPlugin抽離成一個(gè)或幾個(gè)單獨(dú)的js文件。緩存在本地,減少http請求
3 通過UglifyPlugin丑化文件,縮小文件體積
4 通過optimizeCssAssetsPlugin處理css文件,可以大幅度減少css文件體積
5 通過code spliting(es6的import()方法可以實(shí)現(xiàn))實(shí)現(xiàn)按需加載,加速首屏加載
6 通過inlineManifestPlugin將體積較小的manifest文件內(nèi)聯(lián)在html中,避免多余的http請求
7 根據(jù)文件內(nèi)容生成hash,用hash作為文件名。內(nèi)容變化則hash變化,本地緩存失效,重新請求http。內(nèi)容不變化情況下,使用本地緩存,減少http請求(有output的filename、chunkFilename的chunkHash、ExtractTextPlugin的contenthash,圖片等資源的hash)
8 通過hashedModulesPlugin處理輸出的文件,避免因文件名hash變化引起的緩存失效,重新請求資源