當引入一個模塊的時候,只引入 咱們使用過的代碼,那些沒引用過的代碼,咱們就不打包了 另外 tree shaking 只支持 es module ,import模塊的引入。 不支持 common js 的引入。css
若是是生產模式html
mode: 'production',
複製代碼
那麼這個tree shaking 默認就是打開的。vue
另外要注意 要在package.json 這邊 額外添加node
"sideEffects": [
"*.css"
]
複製代碼
假設你引入了一個css 文件,卻沒有明確使用它,默認狀況下就把css 文件也去除掉。這可能並非咱們想要的結果,因此在這裏要進行額外處理。另外 babel-polyfill的實現機制是將轉換過的es5代碼掛載在window對象裏面。 因此若是你用了babel,這裏也要額外進行配置。react
若是你是在 開發模式下打包,那麼想要開啓 tree shaking 模式 則額外須要在webpack config 文件中添加配置:jquery
optimization: {
usedExports: true
},
複製代碼
以前咱們的打包文件只有一個。 不論是dev仍是pro 都在一個config文件裏面打包。實際上是很不方便的, 好比 dev模式下 咱們須要hmr, pro不須要, dev模式下須要webdevserver,pro也不須要, dev模式下的sourcemap 更加嚴格,pro模式下更加寬鬆。等等。webpack
爲了使用起來,咱們須要將dev模式和pro 模式下的配置 區分到2個文件內, 而且考慮到這2個模式下雖然有一些配置項是不一樣的,可是還有大部分模塊配置是相同的,因此還須要第三個文件來放這些同樣的配置。git
形如:es6
爲了能讓webpack支持 這種 配置文件 能夠 索引到基礎文件的狀況,咱們還須要額外安裝插件github
cnpm install webpack-merge -D
複製代碼
而後修改一下package.json
"scripts": {
"build": "webpack --config webpack.prod.js",
"start": "webpack-dev-server --config webpack.dev.js"
},
複製代碼
這樣就實現了 dev和pro 分別打包 分別引用不同的配置文件。而後咱們最後看一下 對應的配置文件怎麼寫
webpack dev js :
const webpack = require('webpack')
const merge = require('webpack-merge')
const commonConfig = require('./webpack.common.js')
const devConfig = {
//開發者模式默認會打開src 爲開發者目錄
mode: 'development',
devtool: 'source-map',
devServer: {
contentBase: './dist',
open: true,
hot: true, //開啓hmr 模式
hotOnly: true //就算hmr 沒有生效 也不刷新瀏覽器
},
plugins: [new webpack.HotModuleReplacementPlugin()],
}
module.exports=merge(commonConfig,devConfig)
複製代碼
webpack pro js:
const merge = require('webpack-merge')
const commonConfig = require('./webpack.common.js')
const proConfig = {
//開發者模式默認會打開src 爲開發者目錄
mode: 'production',
devtool: 'cheap-module-source-map',
}
module.exports=merge(commonConfig,proConfig)
複製代碼
最後看一下 公共的 webpack common js :
const HtmlWebpackPlugin = require('html-webpack-plugin');
const path = require('path');
const CleanWebpackPlugin = require('clean-webpack-plugin');
module.exports={
entry: {
main: './src/index.js'
},
module: {
rules: [{
test: /\.(jpg|png|gif)$/,
use: {
loader: 'url-loader',
options: {
name: '[name]_[hash].[ext]',
outputPath: 'images/',
limit: 10240
}
}
}, {
test: /\.(eot|ttf|svg)$/,
use: {
loader: 'file-loader'
}
}, {
test: /\.scss$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
importLoaders: 2
}
},
'sass-loader',
'postcss-loader'
]
}, {
test: /\.css$/,
use: [
'style-loader',
'css-loader',
'postcss-loader'
]
}, {
test: /\.js$/, exclude: /node_modules/, loader: "babel-loader", options: {
presets: [["@babel/preset-env", {
useBuiltIns: 'usage'
}]]
// "plugins": [["@babel/plugin-transform-runtime"],{
// "corejs": 2,
// "helpers": true,
// "regenerator": true,
// "useESModules": false,
// }]
}
}]
},
output: {
filename: '[name].js',
path: path.resolve(__dirname, 'dist')
},
plugins: [new HtmlWebpackPlugin({
template: 'src/index.html'
}), new CleanWebpackPlugin(['dist'])]
}
複製代碼
lodash 這個組件 應該你們都常常用。 很方便。 可是有一個問題就是,當咱們引用這個組件的時候 包大小很差控制。 且每次有更新都得從新下載。影響界面性能。 舉例說明:
例如
import _ from 'lodash'
console.log(_.join(['a','b','c'],''))
複製代碼
假設咱們的代碼是這麼寫的,當咱們某個版本 輸出結果想要改爲 abd的時候,實際上咱們只改了一個參數,可是由於咱們打包出來只有一個文件,因此對於用戶來說,他就是全量更新了一次。
爲了解決這個問題 咱們能夠:
1.再寫一個lodash文件
2.而後這個lodash文件的 做用 只是掛載一下 而已
import _ from 'lodash'
//把lodash 掛載到window 對象上
window._ = _;
// console.log(_.join(['a','b','c'],'adasda'))
複製代碼
console.log(window._.join(['a','b','c'],''))
複製代碼
而後修改一下 咱們的 webpack config 文件
entry: {
lodash: './src/lodash.js',
main: './src/index.js'
},
複製代碼
如此一來,再次打包就變成
如今的狀況就好不少了,由於拆分紅了2個js文件,因此並行加載2個文件 確定是比咱們串行加載一個文件要快的,並且 對於咱們的業務代碼模塊 main.js來講 ,之後無論修改多少次,用戶都只會從新load 這個main.js 而不會去load 這個lodash.js文件。
有那麼一點點 增量更新的意思
因此code splitting 就是代碼拆分,咱們沒有webpack也能夠這麼作 來提升頁面的性能。可是有了webpack之後咱們來作code splitting就會很是簡單。
有了webpack之後 要作代碼拆分 那就太簡單了 只須要再webpack的配置文件中增長:
optimization: {
splitChunks: {
chunks: 'all'
}
},
複製代碼
其餘的不用變,全部代碼拆分的工做 webpack都幫你作好了
上面的例子是同步的,也就是說 對於瀏覽器而言,咱們是先加載的loadsh js文件,而後再調用咱們的業務方法。
那麼還有一種狀況是異步的,咱們來看看 異步的狀況怎麼處理
function getComponent() {
//異步加載 lodash 庫
return import('lodash').then(
({ default: _ }) => {
var element = document.createElement('div')
element.innerHTML = _.join(['a', 'b', 'c'], " ")
return element;
}
)
}
getComponent().then(element => {
document.body.appendChild(element)
})
複製代碼
也是能夠代碼分割的:
以前咱們看到動態加載的js庫的名字 命名是0.js,這樣看起來實際上是不太好的。咱們想要實際的名字這樣看起來更有意義。 這裏就須要用到魔法註釋的功能
function getComponent() {
//異步加載 lodash 庫
return import(/* webpackChunkName:"lodash" */'lodash').then(
({ default: _ }) => {
var element = document.createElement('div')
element.innerHTML = _.join(['a', 'b', 'c'], " ")
return element;
}
)
}
複製代碼
注意看中間的註釋,這個註釋加上去之後 再次打包:
這樣咱們的動態加載的js庫的名字 也有了必定的意義。 固然這裏咱們想作的極限一些 就是不要vendors這個前綴。 看看官網的配置 修改咱們的webpack-config js文件
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendors: false,
default: false
}
}
},
複製代碼
而後再次打包
如今動態加載的庫 也正常了。
這些相似懶加載的功能 實際運用起來 效果仍是不錯的。就是用到的時候才下載, 不用的時候不要下載, 能夠有效提高頁面加載速度。 這與 vue react 等框架裏面的 router的做用有一些相似。
懶加載的特性 跟 import 語法 強相關
惟一要注意的是,在使用相似的技術的時候 必定要記得使用babel等相似的庫 作一下es6 to es5 轉碼的工做。
修改咱們的package.json文件。
"scripts": {
"build": "webpack --config webpack.prod.js",
"start": "webpack-dev-server --config webpack.dev.js",
//主要就是看這裏,輸出了一個stats的json文件
"dev": "webpack --profile --json > stats.json --config webpack.dev.js"
},
複製代碼
再次打包之後 咱們就會看到這個文件了
將這個文件 上傳到 官網分析器
看看官網的分析結果:
咱們也能夠用一些插件來完成 打包過程的分析: webpack-bundle-analyzer 這個是比較經常使用的
optimization: {
splitChunks: {
chunks: 'all'
}
},
複製代碼
這個all的含義就是 不論是同步的代碼 仍是 異步的代碼 webpack都會對他 作代碼分割 若是你不指定這個值得話 他的默認值是async,也就是說 默認狀況下 只有異步的代碼纔會作代碼分割。
看一段簡單的js代碼:
document.addEventListener('click', () => {
const element = document.createElement('div')
element.innerHTML = 'wuyue'
document.body.appendChild(element)
})
複製代碼
做用就是點擊一下頁面 就新增一個div 內容爲wuyue的節點。 那麼這段代碼在咱們打包後的main.js裏面。 但對於 咱們的用戶來講 main.js 東西不少,但實際起做用的 只有這麼一段代碼。且這段代碼也是點擊之後才起做用的,不點擊的話 實際上這段代碼也沒啥用。屬於一個廢加載。
能夠打開chrome 看一下 這個coverage 面板
當咱們從新刷新頁面的時候 能夠看出來 這個main.js 的利用率是78.2。
看這裏
這段代碼的標記是紅色的,也就是說 這段代碼 在頁面首次加載的時候是用不到的。
那麼webpack 有沒有更好的方式能規避掉 這個影響頁面性能的東西呢? 答案是有的。
咱們新建一個click.js 文件
function handleClick(){
const element =document.createElement('div')
element.innerHTML='wuyue'
document.body.appendChild(element)
}
export default handleClick
複製代碼
而後修改一下index.js
document.addEventListener('click', () => {
import('./click.js').then(({ default: func }) => {
func()
})
})
複製代碼
打包之後 再看看咱們的代碼使用率:
使用率 提高了一個點。 並且咱們看看 這個新的listener 裏面的代碼 和咱們以前的代碼是不同的。
咱們這個時候點擊屏幕
能夠看出來,點擊之後 這段js代碼纔會被下載。
因此 webpack本質上 是但願你 多寫一些異步加載的代碼,這樣才能夠增長你頁面的運行速度。 這也就是爲何 webpack 默認的split的配置是async 由於webpack認爲你只有這樣(異步加載)才能真正加快你網頁運行的速度,而同步加載的split最多也就是啓到一個緩存優化的做用,對於首次加載是沒什麼做用的
這裏有人又要問了:
若是你作成這樣的異步下載,那麼對於用戶的點擊事件而言,他點了一個按鈕之後 是否是要等待一段事件纔有反應?由於畢竟 這麼作 之後 相關的代碼都是點擊之後纔會下載下來的。
這確實是一個問題,同時這也是這一個小節 想要解決的問題。
好比你看一下這個網站,首頁有個登陸框。默認是不展現的。 當用戶點擊了 登陸按鈕之後 就會展現 這個模塊了
因此咱們想把登陸部分的js代碼 用異步加載的方式來優化。也就是默認打開這個頁面 登陸的js代碼不下載, 可是當點擊登陸按鈕之後 這部分js代碼才下載。
可是這個邏輯上面已經說了,點擊之後才下載 體驗上可能會更慢一些。
因此這裏惟一的解決方案就是 當頁面首次加載之後空閒的時候 再去加載這個登陸的js 代碼。
這樣既不會影響頁面首次加載的速度,也不會影響點擊登陸按鈕之後的反應。
這時候就要依賴官方的prefetchingpreloading模塊了
prefetch 是指主js 代碼記載完畢之後 頁面空閒了纔會去加載,而preload 是和主js 代碼一塊兒加載。通常狀況下 咱們用prefetch比較多。
用他也很簡單。
加這麼一點魔法註釋便可
document.addEventListener('click', () => {
import(/* webpackPrefetch: true */'./click.js').then(({ default: func }) => {
func()
})
})
複製代碼
而後再次刷新咱們的頁面
能夠看出來 main.js加載完畢之後 纔去加載0.js
總結:
利用緩存來提高頁面性能的手段實際上提高的效果十分有限,而提升js代碼的利用率則效果非凡,利用好webpack提供給咱們的prefetch這個特性可以極大的提高頁面渲染的性能。
webpack 默認狀況下 會把css代碼 也打包到js 文件裏面
如圖:
使用該插件之後:
固然這裏的css代碼 尚未進行壓縮 可使用這個插件 來對css代碼進行壓縮
如圖:
output: {
filename: '[name].[contenthash].js',
path: path.resolve(__dirname, 'dist')
},
複製代碼
加上contenthash之後 纔會 每次打包(當且僅當代碼有變化時,hash值才發生變化)有一個hash值
不然是不會有的。
這樣就能保證代碼有修改之後 每次瀏覽器訪問的代碼都是最新的代碼 由於文件名變動了。不然的話 每次打包出來的名稱都同樣 會致使瀏覽器一直使用緩存文件。
固然咱們這裏的contenthash只要配置在pro環境中便可。
看下面這個問題:
看這個jquery ui. js 這個文件 他裏面用的實際上是jquery的操做符。可是 jquery咱們是在index 文件中引入的。
這是由於webpack的 模塊化打包形成的。要解決這個問題,咱們能夠在ui.js這個文件 引入一下jquery import 一下便可。
可是假設這個js 文件是一個第三方庫,咱們不想改動他的代碼,這種狀況應該怎麼辦?
新增一下這個ProvidePlugin 便可
plugins: [new HtmlWebpackPlugin({
template: 'src/index.html'
}), new CleanWebpackPlugin(['dist']), new MiniCssExtractPlugin(), new webpack.ProvidePlugin({
$: 'jquery' //發現 有$ 這個字符串 就自動引入jquery 這個庫
})],
複製代碼
shimming 這個東西 在咱們引用一些老的第三方庫的時候會常常遇到,有須要的同窗這裏建議通讀一遍官方文檔