webpack的打包順序:css
var path = require('path');
module.exports = {
entry: {
one: "./src/one.js",
two: "./src/two.js"
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: "[name].js"
}
};
複製代碼
1,找到入口文件html
2,根據入口文件,找出具備依賴關係的文件js/cssnode
3,最後,把css/js所有打包成一個js包react
好的,打包完成,打包了整個世界,那麼問題來了:jquery
產品說:按鈕顏色不對,給我改爲#cccwebpack
技術:好的,這就改。ios
而後就有了以下流程:web
1,找到了entry -> js -> componet -> button.less修改了一個色值json
2,執行webpack打包redux
1,明明只是修改了一個色值,卻要從入口開始從新打包
2,業務代碼明明沒有變化,卻也被牽連了
3,最後生成的js要所有推到線上,覆蓋掉線上本來沒問題的業務js,純粹是增長風險
首先想到的是,既然只修改一個文件,那能不能從新打包一個文件呢?
這種方案,很快就被自我否認了。
由於:
1,從入口打包的文件,已經經過依賴關係,把老版本button.less打入了最終輸出的js文件
2,單獨打包button.less輸出了一個獨立的button.js,這個文件須要手動引入到html中,一旦這類文件製造的多了,根本沒法維護
通過反覆思考,單獨打包每個文件的想法,不符合webpack的設計初衷,從入口打包的流程是不可以產生變化的
在這個問題上卡了真的好久.....好久
因爲我面臨的場景是多頁應用,因此存在多個入口,那麼既然如此,那麼可否經過依賴關係,找到須要更新的入口呢?
這種方案,也思索了好久,後來也被否認了。
由於:
1,webpack沒有適合輸出模塊依賴關係的插件,遍尋無果啊
2,經過webpack的stats分析指標,可以輸出依賴關係,但數據量太大,若是不加過濾,目前項目輸出12W行json信息,還須要花力氣處理一遍這個信息才能拿到關係
3,若是一個組件被多個入口引用,那麼須要找到每個引用的入口點,再從新打包每一個被波及的入口
上面尤爲是第三點,徹底不符合咱們想增量發佈的目的,若是改了一個button組件,要從新打包二三十個入口,這徹底沒有增量發佈的意義
在這個問題上又糾結了好久......好久
通過前面兩個問題後,我發現思考的方向徹底是錯誤的,老是妄想改變webpack的打包方式,簡直就是跟它的理念對着幹。
既然不能改變webpack的打包方式,那麼我可否改變webpack的輸出結果呢?
其實webpack關於緩存方面的功能,提供了不少功能強大的插件,例如:
CommonsChunkPlugin能夠用來在打包的時候提取公共js代碼
ExtractTextPlugin能夠用來從js中提出css,將其輸出到一個獨立的文件
利用這兩個插件,咱們可以將咱們打包的精度加以劃分,將公共引用的部分打包爲一個單獨的文件
若是公共引用的部分變爲了一個單獨的文件,再添加上hash進行緩存,當再次修改的時候只要更新hash,這樣咱們不就可以肯定,究竟改動了哪一個文件了嗎
既然如此,咱們一步一步進行探索:
如今咱們建立測試入口文件:
src/one.js:
import jquery from 'jquery';
console.log('one');
複製代碼
src/two.js:
import jquery from 'jquery';
console.log('two');
複製代碼
webpack.config.js
var path = require('path');
module.exports = {
entry: {
one: "./src/one.js",
two: "./src/two.js"
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: "[name].js"
}
};
複製代碼
執行webpack
輸出了2個文件,大小都是271kb,這是由於one.js和two.js都引用了jquery,jquery打包了2次,分別打包到了兩個文件中
這樣顯然不是很友好,像jquery這種文件,顯然平時不會改動,仍是緩存起來比較好,修改webpack.config.js
var webpack = require("webpack");
var path = require('path');
module.exports = {
entry: {
one: "./src/one.js",
two: "./src/two.js"
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: "[name].js"
},
plugins:[
new webpack.optimize.CommonsChunkPlugin({
name: "common",
}),
]
};
複製代碼
如今咱們添加了CommonsChunkPlugin插件,它的做用是提取公共js,再次執行webpack
能夠看到one.js和two.js的大小已經不到1k了,而common則274k,能夠看到jquery已經被打包到了common.js當中
var webpack = require("webpack");
var path = require('path');
module.exports = {
entry: {
one: "./src/one.js",
two: "./src/two.js"
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: "[name].[hash:6].js"
},
plugins:[
new webpack.optimize.CommonsChunkPlugin({
name: "common",
}),
]
};
複製代碼
上面修改了output的輸出內容[name].[hash].js
如今執行webpack:
能夠看到打包的三個文件都有了hash,但須要主意,此時每一個文件的hash都是同樣的
再次執行一遍webpack:
能夠看到,兩次構建輸出的結果一致,這很好,由於沒有修改文件,天然不但願hash發生改變
那麼接下來,修改一下文件:one.js
import jquery from 'jquery';
console.log('修改one');
複製代碼
悲劇了,全部文件所有修改了hash,查看輸出的結果:
能夠發現只修改一個文件,卻修改了所有文件的hash,這個問題很嚴重,顯然不是咱們想要的
webpack中關於緩存,提供了好幾種添加hash的方法,其中就有chunkhash
chunkhash簡單來講,就是根據模塊內容來添加hash,既然這樣的話,只要文件沒有改變,就不會生成新的hash
var webpack = require("webpack");
var path = require('path');
module.exports = {
entry: {
one: "./src/one.js",
two: "./src/two.js"
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: "[name].[chunkhash:8].js"
},
plugins:[
new webpack.optimize.CommonsChunkPlugin({
name: "common",
}),
]
};
複製代碼
如上圖,修改filename:[name].[chunkhash:8]/js
執行webpack
能夠看到這一次生成的hash是4897....
可是輸出的每一個文件的hash卻不是4897....
很好,接下來再執行一次webpack:
能夠看到兩次輸出之間hash並無發生變化
如今,修改one.js,再執行webapck
import jquery from 'jquery';
console.log('使用chunkhash後修改one');
複製代碼
能夠看到two.js的hash沒有改變one.js的hash改變了,但common.js的hash居然也改了...
前面用CommonsChunkPlugin提取代碼後,公共的代碼已經被抽離,可是他們之間確定存在一個映射關係,例如
之因此commonjs的hash會變,是由於修改one.js生成了新的hash,而jquery又與one.js存在映射關係,映射關係會更新
,也就是說common.js它要重新的one.js中提取了jquery
而manifest
就能夠簡單理解爲模塊映射關係的集合,而這個manifest將隨着這些被分離出來的代碼共同打包!!!
因此如今分離manifest
var webpack = require("webpack");
var path = require('path');
module.exports = {
entry: {
one: "./src/one.js",
two: "./src/two.js"
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: "[name].[chunkhash:8].js"
},
plugins:[
new webpack.optimize.CommonsChunkPlugin({
name: "common",
}),
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest' // 用於提取manifest
})
]
};
複製代碼
這裏主要是利用CommonsChunkPlugin的一個功能,經過默認的名字,來提取公共代碼,由於webpack打包的是有有一個默認模塊就是manifest,因此咱們能夠經過這個來實現
如今咱們執行webpack:
能夠看到,多輸出了一個manifest.js
接下來,再修改one.js
import jquery from 'jquery';
console.log('分離manifest後修改one');
複製代碼
能夠看到,如今只有one.js和manifest.js的hash發生了改變,common.js被成功緩存了
使用代碼對比工具,比較兩次manifest之間的區別,能夠看到確實是映射的chunkid發生了改變
前面咱們輸出了一個manifest.js,但這樣還須要單獨處理這個manifest.js,因此可使用webpack的另外一個插件webpack-md5-hash
var webpack = require("webpack");
var WebpackMd5Hash = require('webpack-md5-hash');
var path = require('path');
module.exports = {
entry: {
one: "./src/one.js",
two: "./src/two.js"
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: "[name].[chunkhash:8].js"
},
plugins:[
new WebpackMd5Hash(),
new webpack.optimize.CommonsChunkPlugin({
name: "common",
}),
]
};
複製代碼
執行一次打包:
沒有manifest輸出,修改one.js
import jquery from 'jquery';
console.log('使用WebpackMd5Hash修改one');
複製代碼
再次打包:
這一次僅有one.js的hash發生了改變
雖然webpack-md5-hash解決了咱們的問題,但這也讓打包的模塊關係變成了黑盒,存在必定的未知風險,還須要仔細實踐評估是否有問題
前面已經抽離出來了公共代碼,可是還存在問題,假如這時候又須要引入lodash,那common的hash是否會改變?
修改one.js
import jquery from 'jquery';
import lodash from 'lodash';
console.log('引入lodash修改one');
複製代碼
修改two.js
import jquery from 'jquery';
import lodash from 'lodash';
console.log('引入lodash修改two');
複製代碼
這一次,全部文件的hash都發生了改變,不只如此,並且更顯著的是common的體積增大了
這就意味者lodash也被打進了common當中,但這自己是一個錯誤的行爲,lodash和jquery,平時根本不會對其進行修改,既然如此,那還須要優化,把他們單獨打包出去
如今修改webapack.config.js
var webpack = require("webpack");
var WebpackMd5Hash = require('webpack-md5-hash');
var path = require('path');
module.exports = {
entry: {
two: "./src/two.js",
one: "./src/one.js",
common:['jquery','lodash']
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: "[name].[chunkhash:8].js"
},
plugins:[
new WebpackMd5Hash(),
new webpack.optimize.CommonsChunkPlugin({
name: "common",
}),
]
};
複製代碼
這一次在入口處添加了一個common,common單獨指向了jquery和lodash,這一次咱們執行打包
此時,輸出的內容沒有明顯變化,一樣是3個文件,大小也徹底一致,hash也沒有問題
能夠看到,common的大小是817k
若是這時,再應用了其餘的包呢?例如引入react
修改one.js
import jquery from 'jquery';
import lodash from 'lodash';
import react from 'react';
console.log('引入react修改one');
複製代碼
修改two.js
import jquery from 'jquery';
import lodash from 'lodash';
import react from 'react';
console.log('引入react修改one');
複製代碼
執行webpack
問題來了,common的大小增長了,很顯然react被打包進去了,但若是咱們此時,只想永久緩存jquery和lodash呢,這該怎麼辦?
修改webpack.config.js
var webpack = require("webpack");
var WebpackMd5Hash = require('webpack-md5-hash');
var path = require('path');
module.exports = {
entry: {
two: "./src/two.js",
one: "./src/one.js",
common:['jquery','lodash']
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: "[name].[chunkhash:8].js"
},
plugins:[
new WebpackMd5Hash(),
new webpack.optimize.CommonsChunkPlugin({
name: 'common',
minChunks:Infinity
})
]
};
複製代碼
這一次,添加了一句話minChunks:Infinity
minChunks屬性的能夠設置爲2,意思是引用次數爲2的模塊就抽離出來,而Infinity
則表示無限,無限就意味着不會有多餘的被打包進來
如今執行webpack打包
能夠看到如今common又恢復了816k,固然react也沒有抽出來,還在兩個文件當中,接下來繼續抽離react
var webpack = require("webpack");
var WebpackMd5Hash = require('webpack-md5-hash');
var path = require('path');
module.exports = {
entry: {
two: "./src/two.js",
one: "./src/one.js",
common:['jquery','lodash'],
react:['react','react-redux']
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: "[name].[chunkhash:8].js"
},
plugins:[
new webpack.optimize.CommonsChunkPlugin({
name: ['react','common'], // 用於提取manifest
minChunks:Infinity
}),
new WebpackMd5Hash(),
]
};
複製代碼
經過上面的構建,咱們已經將不會改動的類庫,單獨打包並維持住了hash。
前面看似完美,但若是咱們如今改變一下入口的順序
entry: {
react:['react','react-redux'],
two: "./src/two.js",
one: "./src/one.js",
common:['jquery','lodash'],
}
複製代碼
能夠看到common和react公共庫的hash又變了,這是由於,模塊id是根據webpack的解析順序增量的,若是變換解析順序,那模塊id也會隨之改變。
因此就須要HashedModuleIdsPlugin了,它是根據模塊相對路徑生成模塊標識,若是模塊沒有改變,那模塊標識也不會改變
var webpack = require("webpack");
var WebpackMd5Hash = require('webpack-md5-hash');
var path = require('path');
module.exports = {
entry: {
common:['jquery','lodash'],
react:['react','react-redux'],
two: "./src/two.js",
one: "./src/one.js",
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: "[name].[chunkhash:8].js"
},
plugins:[
new webpack.optimize.CommonsChunkPlugin({
name: ['react','common'], // 用於提取manifest
minChunks:Infinity
}),
new webpack.HashedModuleIdsPlugin(),
new WebpackMd5Hash(),
]
};
複製代碼
如今打包後,模塊的標識再也不是id了,而是一個四位的編碼了,這樣就能夠固定住ip地址了。
在src下建立one.css:
body{
color:blue;
}
複製代碼
two.css
h1{
font-size:24px;
}
複製代碼
修改one.js和two.js引入css
import jquery from 'jquery';
import lodash from 'lodash';
import react from 'react';
import './one.css'
console.log('引入css修改one');
複製代碼
修改webpack.config.js
var webpack = require("webpack");
var WebpackMd5Hash = require('webpack-md5-hash');
var path = require('path');
var ExtractTextPlugin = require("extract-text-webpack-plugin");
module.exports = {
entry: {
common: ['jquery', 'lodash'],
react: ['react', 'react-redux'],
two: "./src/two.js",
one: "./src/one.js",
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: "[name].[chunkhash:8].js"
},
module: {
rules: [
{
test: /\.css$/,
use: ExtractTextPlugin.extract({
fallback: "style-loader",
use: "css-loader"
})
}
]
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: ['react', 'common'], // 用於提取manifest
minChunks: Infinity
}),
new ExtractTextPlugin("[name].[chunkhash:8].css"),
new webpack.HashedModuleIdsPlugin(),
new WebpackMd5Hash()
]
};
複製代碼
執行webpack:
能夠看到,成功輸出了js和css,可是有點疑問的是,one.css和one.js的hash是同樣的,這樣的話,若是咱們改變one.css呢?
修改one.css,再次打包:
發現css的hash沒有任何變化。
接着再修改one.js,再次打包:
這一次one.js和one.css的hash同時改變了。
webpack output文檔種有寫,當提取css後,用contenthash添加hash
var webpack = require("webpack");
var WebpackMd5Hash = require('webpack-md5-hash');
var path = require('path');
var ExtractTextPlugin = require("extract-text-webpack-plugin");
module.exports = {
entry: {
common: ['jquery', 'lodash'],
react: ['react', 'react-redux'],
two: "./src/two.js",
one: "./src/one.js",
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: "[name].[chunkhash:8].js"
},
module: {
rules: [
{
test: /\.css$/,
use: ExtractTextPlugin.extract({
fallback: "style-loader",
use: "css-loader"
})
}
]
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: ['react', 'common'], // 用於提取manifest
minChunks: Infinity
}),
new ExtractTextPlugin("[name].[contenthash:8].css"),
new webpack.HashedModuleIdsPlugin(),
new WebpackMd5Hash()
]
};
複製代碼
這一次,只是修改了輸出的hash,conenthash表明的是文本文件內容的hash值,也就是隻有style文件的hash值。
執行webpack:
one.js和one.css的hash變的不同了
接下來,修改one.css
body{
color:white;
}
複製代碼
再次執行webpack:
至此,只有one.css發生了變化,準備工做基本就到這裏了
由於是多頁應用,是經過掃入口文件來進行的打包,規則爲js文件爲入口文件,jsx爲引用的資源不被識別爲入口
經過BundleAnalyzerPlugin插件分析,發現有部分組件被打包爲了入口,梳理一遍後,從新打包,打包時間減小了2/3,固然這是在填之前的坑
生產打包時間是74578ms
此時壓縮和不壓縮的打包時間也是3倍的關係:
開發打包時間是24780ms
好的,圍繞這兩個時間,咱們開始優化
首先要作的實際上是穩定hash,但由於生產環境的打包速度太慢,因此咱們先優化打包速度,webpack默認提供的打包是單線程的
const UglifyJSPlugin = require('uglifyjs-webpack-plugin')
module.exports = {
plugins: [
new UglifyJSPlugin({
parallel: true
})
]
}
複製代碼
這個插件是webpack3提供的,至於低版本webapck的話,須要謹慎處理,不過效果很明顯
如今生產打包時間是51690ms
,比以前提速了1/3
var HappyPack = require('happypack');
var os = require('os');
var happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length });
...
module: {
rules: [ {
test: /\.js[x]?$/,
exclude: /(node_modules|bower_components)/,
loader: 'happypack/loader?id=happybabel',
include: path.join(__dirname, 'static/assets/js')
}
}
plugins: [
new HappyPack({
id: 'happybabel',
loaders: ['babel-loader?cacheDirectory=true'],
threadPool: happyThreadPool,
cache: true,
verbose: true
}),
複製代碼
上面module的rules屬性中loader本來事babel-loader,如今將它變成了一個任務,其中有一個id,id對應的就是plugins中的happyPack實例
此時,咱們開啓了babel-loader的多線程模式
如今生產打包時間是43855ms
,比以前又提速了1/9,這只是babel-loader,咱們還能夠爲其它的loader開啓
接着處理less,css,style等loader,這些結合能夠一口氣搞定
module: {
rules: [{
test: require.resolve('zepto'),
loader: 'exports-loader?window.Zepto!script-loader'
}, {
test: /\.js[x]?$/,
exclude: /(node_modules|bower_components)/,
loader: 'happypack/loader?id=happybabel',
include: path.join(__dirname, 'static/assets/js')
}, {
test: /\.less$/,
use: extractTextPlugin.extract({
fallback: "style-loader",
// use: ["css-loader" + (ENV ? '?minimize' : ''), "less-loader", "postcss-loader"]
use: ["happypack/loader?id=postcss"]
})
}]
}
plugins: [
new HappyPack({
id: 'happybabel',
loaders: ['babel-loader?cacheDirectory=true'],
threadPool: happyThreadPool,
// cache: true,
verbose: true
}),
new HappyPack({
id: 'postcss',
loaders: ["css-loader" + (ENV ? '?minimize' : ''), "less-loader",'postcss-loader'],
threadPool: happyThreadPool,
// cache: true,
verbose: true
}),
複製代碼
這樣,咱們即處理了babel,同時也搞定了css,less,postcss這些loader
上圖happy[任務名],能夠看到打包行爲全都開啓了多線程,效果顯著
如今生產打包時間是35130ms
,此時已經比第一此非優化的時候,提高了一倍的速度
通過前面的過程,想必已經意識到了純靜態得庫和組件都須要與打包環節分離開,這就須要dll技術了
dll技術,其實就是將修改頻率低或基本不修改且引用次數多的內容,單獨打包
由於設計dll後,config文件的數量劇增,因此須要從新整理目錄結構
例如上圖,將每個webpack拆分出去,把全部配置文件分離開,例webpack.dev.js:
var base = require('./webpack.base.js');
var config = {
entry: require('./dev/entry.js'),
output: require('./dev/output.js'),
plugins: require('./dev/plugins.js'),
devtool: 'eval-source-map'
}
//把配置文件暴露出去;
module.exports = Object.assign(base,config);
複製代碼
ok,基礎拆分webpack完成後,咱們建立一個webpack.dll.libs.js用於打包類庫
module.exports = {
libs: [
'react',
'react-dom',
'react-motion',
'react-redux',
'redux',
'axios',
'prop-types',
'classnames',
]
}
複製代碼
修改plugins插件:
var webpack = require('webpack');
var dirVars = require('../common/dir.js');
var path = require('path');
var UglifyJsPlugin = require('uglifyjs-webpack-plugin');//多線程打包
var getDefaultPlugins = require('../common/plugins.js').getDefaultPlugins;
var AssetsPlugin = require('assets-webpack-plugin');//輸出映射表
var plugins =[
new webpack.DllPlugin({
path: dirVars.dllLibsManiFest,
}),
new UglifyJsPlugin({
parallel: true,
cache: true
}),
new AssetsPlugin({
filename: 'static/dll/libs-rev-manifest.json'
}),
]
module.exports = plugins.concat(getDefaultPlugins())
複製代碼
如今執行webpack
能夠看到,只須要1s,就打包了全部的類庫,接下來,修改webpack.prod.js
在plugins中添加:
new webpack.DllReferencePlugin({
manifest: 'static/dll/libs-rev-manifest.json'
}),
複製代碼
此時當咱們執行webpack.prod.js進行打包,當掃描到libs中的打包的內容時,就不會重複打包
前面已經完全搞定了打包,但破壞性很大,因此須要系統的驗證hash是否存在問題
case1:js改變
修改一個業務代碼的js,添加一句註釋,再次打包
能夠看到文件hash發生了改變,但很不幸,vendor也發生了改變
解決方案:添加webpack-md5-hash插件,使用以後,再次驗證,發現vendorjs的hash再也不發生變化
case2:less改變
只有一個css的hash發生了變化,沒問題
case3:修改一個入口下本身封裝出去的公共方法
上面修改了一個入口內公共使用的tools插件,最終是入口的hash發生了改變,沒問題
case4:修改公共方法組件js
主要是多個入口都會引用的組件
測試,只有單獨打包出去的components的hash修改了
case5:修改公共方法組件less
只有一個hash發生了改變
case6:添加一個公共組件
只有components的hash發生了改變
未優化前打包時間180-200s
優化:
1,約束入口,嚴格明確入口文件篩選條件後
生產打包:74578ms
開發打包:24780ms
2,開啓多線程壓縮後
生產打包:51690ms
3,開啓多線程編譯
生產打包:35130ms
開發打包:15031ms
4,拆包
分解了打包過程,類庫4s,組件4s,業務20s,整體30s左右
複製代碼
最終,流程變得可控,打包實現了定製化,hash獲得了保持。