2018/2/25,webpack4正式發佈,距離如今已通過去三個多月了,也逐漸趨於穩定,並且如今的最新版本都到了4.12.0(版本迭代快得真是讓人懼怕)。javascript
不少人都說webpack複雜,難以理解,很大一部分緣由是webpack是基於配置的,可配置項不少,而且每一個參數傳入的形式多種多樣(能夠是字符串、數組、對象、函數。。。),文檔介紹也比較模糊,這麼多的配置項各類排列組合,想一想都複雜。而gulp基於流的方式來處理文件,不管從理解上,仍是功能上都很容易上手。css
//gulp
gulp.src('./src/js/**/*.js')
.pipe('babel')
.pipe('uglifyjs')
.dest('./dist/js')
//webpack
module.exports = {
entry: './src/main.js',
output: __dirname + '/dist/app.js',
module: {
rules: [{
test: /\.js$/,
loader: 'babel-loader'
}]
},
plugins: [
new require('uglifyjs-webpack-plugin')()
]
}
複製代碼
上面簡單對比了webpack與gulp配置的區別,固然這樣比較是有問題的,gulp並不能進行模塊化的處理。這裏主要是想告訴你們使用gulp的時候,咱們能明確的知道js文件是先進行babel轉譯,而後進行壓縮混淆,最後輸出文件。而webpack對咱們來講徹底是個黑盒,徹底不知道plugins的執行順序。正是由於這些緣由,咱們經常在使用webpack時有一些不安,不知道這個配置到底有沒有生效,我要按某種方式打包到底該如何配置?html
爲了解決上面的問題,webpack4引入了零配置
的概念(Parcel ???),實際體驗下來仍是要寫很多配置。 可是這不是重點,重點是官方宣傳wbpack4可以提高構建速度60%-98%,真的讓人心動。前端
首先安裝最新版的webpack和webpack-dev-server,而後再安裝webpack-cli。webpack4將命令行相關的操做抽離到了webpack-cli中,因此,要使用webpack4,必須安裝webpack-cli。固然,若是你不想使用webpack-cli,社區也有替代方案webpack-command,雖然它與webpack-cli區別不大,可是仍是建議使用官方推薦的webpack-cli。vue
npm i webpack@4 webpack-dev-server@3 --save-dev
npm i webpack-cli --save-dev
複製代碼
webpack-cli除了能在命令行接受參數運行webpack外,還具有migrate
和init
功能。java
$ webpack-cli migrate ./webpack.config.js
✔ Reading webpack config
✔ Migrating config from v1 to v2
- loaders: [
+ rules: [
- loader: 'babel',
- query: {
+ use: [{
+ loader: 'babel-loader'
+ }],
+ options: {
- loader: ExtractTextPlugin.extract('style', 'css!sass')
+ use: ExtractTextPlugin.extract({
+ fallback: 'style',
+ use: 'css!sass'
+ })
? Are you sure these changes are fine? Yes
✔︎ New webpack v2 config file is at /home/webpack-cli/build/webpack.config.js
複製代碼
webpack-cli init
1. Will your application have multiple bundles? No // 若是是多入口應用,能夠傳入一個object
2. Which module will be the first to enter the application? [example: './src/index'] ./src/index // 程序入口
3. What is the location of "app"? [example: "./src/app"] './src/app'
4. Which folder will your generated bundles be in? [default: dist]
5. Are you going to use this in production? No
6. Will you be using ES2015? Yes //是否使用ES6語法,自動添加babel-loader
7. Will you use one of the below CSS solutions? SASS // 根據選擇的樣式類型,自動生成 loader 配置
8. If you want to bundle your CSS files, what will you name the bundle? (press enter to skip)
9. Name your 'webpack.[name].js?' [default: 'config']: // webpack.config.js
Congratulations! Your new webpack configuration file has been created!
複製代碼
更詳細介紹請查看webpack-cli的文檔node
零配置就意味着webpack4具備默認配置,webpack運行時,會根據mode
的值採起不一樣的默認配置。若是你沒有給webpack傳入mode,會拋出錯誤,並提示咱們若是要使用webpack就須要設置一個mode。react
The 'mode' option has not been set, webpack will fallback to 'production' for this value. Set 'mode' option to 'development' or 'production' to enable defaults for each environment. You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/concepts/mode/webpack
mode一共有以下三種配置:git
none
這個配置的意思就是不使用任何默認配置
development,開發環境下的默認配置
module.exports = {
//開發環境下默認啓用cache,在內存中對已經構建的部分進行緩存
//避免其餘模塊修改,可是該模塊未修改時候,從新構建,可以更快的進行增量構建
//屬於空間換時間的作法
cache: true,
output: {
pathinfo: true //輸入代碼添加額外的路徑註釋,提升代碼可讀性
},
devtools: "eval", //sourceMap爲eval類型
plugins: [
//默認添加NODE_ENV爲development
new webpack.DefinePlugin({ "process.env.NODE_ENV": JSON.stringify("development") }),
],
optimization: {
namedModules: true, //取代插件中的 new webpack.NamedModulesPlugin()
namedChunks: true
}
}
複製代碼
module.exports = {
performance: {
hints: 'warning',
maxAssetSize: 250000, //單文件超過250k,命令行告警
maxEntrypointSize: 250000, //首次加載文件總和超過250k,命令行告警
}
plugins: [
//默認添加NODE_ENV爲production
new webpack.DefinePlugin({ "process.env.NODE_ENV": JSON.stringify("production") })
],
optimization: {
minimize: true, //取代 new UglifyJsPlugin(/* ... */)
providedExports: true,
usedExports: true,
//識別package.json中的sideEffects以剔除無用的模塊,用來作tree-shake
//依賴於optimization.providedExports和optimization.usedExports
sideEffects: true,
//取代 new webpack.optimize.ModuleConcatenationPlugin()
concatenateModules: true,
//取代 new webpack.NoEmitOnErrorsPlugin(),編譯錯誤時不打印輸出資源。
noEmitOnErrors: true
}
}
複製代碼
其餘的一些默認值:
module.exports = {
context: process.cwd()
entry: './src',
output: {
path: 'dist',
filename: '[name].js'
},
rules: [
{
type: "javascript/auto",
resolve: {}
},
{
test: /\.mjs$/i,
type: "javascript/esm",
resolve: {
mainFields:
options.target === "web" ||
options.target === "webworker" ||
options.target === "electron-renderer"
? ["browser", "main"]
: ["main"]
}
},
{
test: /\.json$/i,
type: "json"
},
{
test: /\.wasm$/i,
type: "webassembly/experimental"
}
]
}
複製代碼
若是想查看更多webpack4相關的默認配置,到這裏來。能夠看到webpack4把不少插件相關的配置都遷移到了optimization中,可是咱們看看官方文檔對optimization的介紹簡直寥寥無幾,而在默認配置的代碼中,webpack對optimization的配置有十幾項,反正我是怕了。
雖然api發生了一些變化,好的一面就是有了這些默認值,咱們想經過webpack構建一個項目比之前要簡單不少,若是你只是想簡單的進行打包,在package.json中添加以下兩個script,包你滿意。
{
"scripts": {
"dev": "webpack-dev-server --mode development",
"build": "webpack --mode production"
},
}
複製代碼
開發環境使用webpack-dev-server,邊預覽邊打包不再用f5,簡直爽歪歪;生產環境直接生成打包後的文件到dist目錄
loader的升級就是一次大換血,以前適配webpack3的loader都須要升級才能適配webpack4。若是你使用了不兼容的loader,webpack會告訴你:
DeprecationWarning: Tapable.apply is deprecated. Call apply on the plugin directly instead
DeprecationWarning: Tapable.plugin is deprecated. Use new API on
.hooks
instead
若是在運行過程當中遇到這兩個警告,就表示你有loader或者plugin沒有升級。形成這兩個錯誤的緣由是,webpack4使用的新的插件系統,而且破壞性的對api進行了更新,不過好在這只是警告,不會致使程序退出,不過建議最好是進行升級。對於loader最好所有進行一次升級,反正也不虧,百利而無一害。
關於plugin,有兩個坑,一個是extract-text-webpack-plugin
,還一個是html-webpack-plugin
。
先說說extract-text-webpack-plugin
,這個插件主要用於將多個css合併成一個css,減小http請求,命名時支持contenthash(根據文本內容生成hash)。可是webpack4使用有些問題,因此官方推薦使用mini-css-extract-plugin
。
⚠️ Since webpack v4 the extract-text-webpack-plugin should not be used for css. Use mini-css-extract-plugin instead.
這裏改動比較小,只要替換下插件,而後改動下css相關的loader就好了:
-const ExtractTextPlugin = require('extract-text-webpack-plugin')
+const MiniCssExtractPlugin = require('mini-css-extract-plugin')
module.exports = {
module: {
rules: [
{
test: /\.css$/,
- use: ExtractTextPlugin.extract({
- use: [{
- loader: 'css-loader',
- options: {
- minimize: process.env.NODE_ENV === 'production'
- }
- }],
- fallback: 'vue-style-loader'
- })
+ use: [
+ MiniCssExtractPlugin.loader,
+ {
+ loader: 'css-loader',
+ options: {
+ minimize: process.env.NODE_ENV === 'production'
+ }
+ }
+ ],
}
]
},
plugins:[
- new ExtractTextPlugin({
+ new MiniCssExtractPlugin({
filename: 'css/[name].css',
}),
...
]
}
複製代碼
而後看看html-webpack-plugin
,將這個插件升級到最新版本,通常狀況沒啥問題,可是有個坑,最好是把chunksSortMode
這個選項設置爲none。
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
plugins:[
new HtmlWebpackPlugin({
filename: 'index.html',
template: 'index.html',
inject: true,
hash: true,
chunksSortMode: 'none' //若是使用webpack4將該配置項設置爲'none'
})
]
}
複製代碼
官方有個issues討論了這個問題,感興趣能夠去看看。目前做者還在尋找解決方案中。
另外,webpack-dev-server也有個升級版本,叫作webpack-serve,功能比webpack-dev-server強大,支持HTTP二、使用WebSockets作熱更新,暫時還在觀望中,後續採坑。
webpack3中,咱們常用CommonsChunkPlugin
進行模塊的拆分,將代碼中的公共部分,以及變更較少的框架或者庫提取到一個單獨的文件中,好比咱們引入的框架代碼(vue、react)。只要頁面加載過一次以後,抽離出來的代碼就能夠放入緩存中,而不是每次加載頁面都從新加載所有資源。
CommonsChunkPlugin的常規用法以下:
module.exports = {
plugins: [
new webpack.optimize.CommonsChunkPlugin({ //將node_modules中的代碼放入vendor.js中
name: "vendor",
minChunks: function(module){
return module.context && module.context.includes("node_modules");
}
}),
new webpack.optimize.CommonsChunkPlugin({ //將webpack中runtime相關的代碼放入manifest.js中
name: "manifest",
minChunks: Infinity
}),
]
}
複製代碼
以前CommonsChunkPlugin
雖然能用,可是配置不夠靈活,難以理解,minChunks有時候爲數字,有時候爲函數,而且若是同步模塊與異步模塊都引入了相同的module並不能將公共部分提取出來,最後打包生成的js仍是存在相同的module。
如今webpack4使用optimization.splitChunks
來進行代碼的拆分,使用optimization.runtimeChunk
來提取webpack的runtime代碼,引入了新的cacheGroups
概念。而且webpack4中optimization提供以下默認值,官方稱這種默認配置是保持web性能的最佳實踐,不要手賤去修改,就算你要改也要多測試(官方就是這麼自信)。
module.exports = {
optimization: {
minimize: env === 'production' ? true : false, //是否進行代碼壓縮
splitChunks: {
chunks: "async",
minSize: 30000, //模塊大於30k會被抽離到公共模塊
minChunks: 1, //模塊出現1次就會被抽離到公共模塊
maxAsyncRequests: 5, //異步模塊,一次最多隻能被加載5個
maxInitialRequests: 3, //入口模塊最多隻能加載3個
name: true,
cacheGroups: {
default: {
minChunks: 2,
priority: -20
reuseExistingChunk: true,
},
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10
}
}
},
runtimeChunk {
name: "runtime"
}
}
}
複製代碼
有了這些默認配置,咱們幾乎不須要任何成功就能刪除以前CommonChunkPlugin的代碼,好神奇。
經過判斷splitChunks.chunks
的值來肯定哪些模塊會提取公共模塊,該配置一共有三個選項,initial
、async
、 all
。 默認爲async,表示只會提取異步加載模塊的公共代碼,initial表示只會提取初始入口模塊的公共代碼,all表示同時提取前二者的代碼。
這裏有個概念須要明確,webpack中什麼是初始入口模塊,什麼是異步加載模塊。e.g.
//webpack.config.js
module.exports = {
entry: {
main: 'src/index.js'
}
}
//index.js
import Vue from 'vue'
import(/* webpackChunkName: "asyncModule" */'./a.js')
.then(mod => {
console.log('loaded module a', mod)
})
console.log('initial module')
new Vue({})
//a.js
import _ from 'lodash'
const obj = { name: 'module a' }
export default _.clone(obj)
複製代碼
上面的代碼中,index.js
在webpack的entry配置中,這是打包的入口,因此這個模塊是初始入口模塊。再看看index.js
中使用了動態import語法,對a.js
(該異步模塊被命名爲asyncModule)進行異步加載,則a.js
就是一個異步加載模塊。再看看index.js
和a.js
都有來自node_modules
的模塊,按照以前的規則,splitChunks.chunks默認爲async
,因此會被提取到vendors中的只有webpackChunkName中的模塊。
若是咱們把splitChunks.chunks改爲all,main中來自node_modules
的模塊也會被進行提取了。
module.exports = {
optimization: {
splitChunks: {
chunks: "all"
}
}
}
複製代碼
如今咱們在index.js
中也引入lodash,看看入口模塊和異步模塊的公共模塊還會不會像CommonsChunkPlugin同樣被重複打包。
//index.js
import Vue from 'vue'
import _ from 'lodash'
import(/* webpackChunkName: "asyncModule" */'./a.js')
.then(mod => {
console.log('loaded module a', mod)
})
console.log('initial module')
console.log(_.map([1,2,3], a => {
return a * 10
}))
new Vue({})
//a.js
import _ from 'lodash'
const obj = { name: 'module a' }
export default _.clone(obj)
複製代碼
能夠看到以前CommonsChunkPlugin的問題已經被解決了,main模塊與asyncModule模塊共同的lodash都被打包進了vendors~main.js
中。
splitChunks.cacheGroups
配置項就是用來表示,會提取到公共模塊的一個集合,也就是一個提取規則。像前面的vendor
,就是webpack4默認提供的一個cacheGroup,表示來自node_modules的模塊爲一個集合。
除了cacheGroups配置項外,能夠看下其餘的幾個默認規則。
對應到代碼中就是這四個配置:
{
minSize: 30000,
minChunks: 1,
maxAsyncRequests: 5,
maxInitialRequests: 3,
}
複製代碼
webpack是一個基於node的前端打包工具,可是node基於v8運行時只能是單線程,可是node中可以fork子進程。因此咱們可使用多進程的方式運行loader,和壓縮js,社區有兩個插件就是專門幹這兩個事的:HappyPack、ParallelUglifyPlugin。
const path = require('path')
module.exports = {
module: {
rules: [
{
test: /\.js$/,
// loader: 'babel-loader'
loader: 'happypack/loader?id=babel'
}
]
},
plugins: [
new require('happypack')({
id: 'babel',
loaders: ['babel-loader']
}),
],
};
複製代碼
module.exports = {
optimization: {
minimizer: [
new require('webpack-parallel-uglify-plugin')({
// 配置項
}),
]
}
}
複製代碼
使windows的時候,咱們常常會看到一些.dll
文件,dll文件被稱爲動態連接庫,裏面包含了程序運行時的一些動態函數庫,多個程序能夠共用一個dll文件,能夠減小程序運行時的物理內存。
webpack中咱們也能夠引入dll的概念,使用DllPlugin插件,將不常常變化的框架代碼打包到一個js中,好比叫作dll.js。在打包的過程當中,若是檢測到某個塊已經在dll.js中就不會再打包。以前DllPlugin與CommonsChunkPlugin並能相互兼容,本是同根生相煎何太急。可是升級到webpack4以後,問題就迎刃而解了。
使用DllPlugin的時候,要先寫另一個webpack配置文件,用來生成dll文件。
//webpack.vue.dll.js
const path = require('path')
module.exports = {
entry: {
// 把 vue 相關模塊的放到一個單獨的動態連接庫
vue: ['vue', 'vue-router', 'vuex', 'element-ui']
},
output: {
filename: '[name].dll.js', //生成vue.dll.js
path: path.resolve(__dirname, 'dist'),
library: '_dll_[name]'
},
plugins: [
new require('webpack/lib/DllPlugin')({
name: '_dll_[name]',
// manifest.json 描述動態連接庫包含了哪些內容
path: path.join(__dirname, 'dist', '[name].manifest.json')
}),
],
};
複製代碼
而後在以前的webpack配置中,引入dll。
const path = require('path')
module.exports = {
plugins: [
// 只要引入manifest.json就能知道哪些模塊再dll文件中,在打包過程會忽略這些模塊
new require('webpack/lib/DllReferencePlugin')({
manifest: require('./dist/vue.manifest.json'),
})
],
devtool: 'source-map'
};
複製代碼
最後生成html文件的時候,必定要先引入dll文件。
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<div id="app"></div>
<script src="./dist/vue.dll.js"></script>
<script src="./dist/main.js"></script>
</body>
</html>
複製代碼
前面的優化都是優化打包速度,或者減小重複模塊的。這裏有一種優化方式,可以減小代碼量,而且減小客戶端的運行時間。
使用Prepack,這是facebook開源的一款工具,可以運行你的代碼中部分可以提早運行的代碼,減小在線上真實運行的代碼。
官方的demo以下:
//input
(function () {
function hello() { return 'hello'; }
function world() { return 'world'; }
global.s = hello() + ' ' + world();
})();
//output
s = "hello world";
複製代碼
想在webpack中接入也比較簡單,社區以及有了對應的插件prepack-webpack-plugin,目前正式環境運用較少,還有些坑,能夠繼續觀望。
module.exports = {
plugins: [
new require('prepack-webpack-plugin')()
]
};
複製代碼
這裏簡單羅列了一些webpack的優化策略,可是有些優化策略仍是仍是要酌情考慮。好比多進程跑loader,若是你項目比較小,開了以後可能變慢了,由於原本打包時間就比較短,用來fork子進程的時間,說不定都已經跑完了。記住過早的優化就是萬惡之源
。
webpack4帶了不少新的特性,也大大加快的打包時間,而且減小了打包後的文件體積。期待webpack5的更多新特性,好比,以html或css爲文件入口(鄙人認爲html纔是前端模塊化的真正入口,瀏覽器的入口就是html,瀏覽器在真正的親爹,不和爹親和誰親),默認開啓多進程打包,加入文件的長期緩存,更多的拓展零配置。
同時也要感謝前端社區其它的優秀的打包工具,感謝rollup,感謝parcel。