webpack配置之代碼優化


前面的話
  前面介紹了webpack的基本配置,本文將詳細介紹webpack中關於代碼優化的配置

打包公共代碼
  CommonsChunkPlugin 插件,是一個可選的用於建立一個獨立文件(又稱作 chunk)的功能,這個文件包括多個入口 chunk 的公共模塊。通過將公共模塊拆出來,最終合成的文件能夠在最開始的時候加載一次,便存到緩存中供后續使用。這會帶來速度上的提升,因為瀏覽器會迅速將公共的代碼從緩存中取出來,而不是每次訪問一個新頁面時,再去加載一個更大的文件

new webpack.optimize.CommonsChunkPlugin(options)
【配置項】

復制代碼
{
name: string, // or
names: string[],
// common chunk 的名稱

filename: string,
// common chunk 的文件名模板。可以包含與 output.filename 相同的占位符

minChunks: number|Infinity|function(module, count) -> boolean,
// 在傳入公共chunk(commons chunk) 之前所需要包含的最少數量的 chunks 。
// 數量必須大於等於2,或者少於等於 chunks的數量

chunks: string[],
// 通過 chunk name 去選擇 chunks 的來源。chunk 必須是 公共chunk 的子模塊。

children: boolean,
// 如果設置為 true,所有公共chunk 的子模塊都會被選擇

deepChildren: boolean,
// If true all descendants of the commons chunk are selected

async: boolean|string,
// 如果設置為 true,一個異步的公共chunk 會作為 options.name 的子模塊,和 options.chunks 的兄弟模塊被創建。

minSize: number,
// 在 公共chunk 被創建立之前,所有公共模塊 (common module) 的最少大小。
}
復制代碼
【提取公共代碼】

復制代碼
new webpack.optimize.CommonsChunkPlugin({
name: "commons",
// ( 公共chunk(commnons chunk) 的名稱)

filename: "commons.js",
// ( 公共chunk 的文件名)
})
復制代碼
【明確第三方庫chunk】

復制代碼
entry: {
vendor: ["jquery", "other-lib"],
app: "./entry"
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: "vendor",
minChunks: Infinity,
})
]
復制代碼
【將公共模塊打包進父 chunk】

new webpack.optimize.CommonsChunkPlugin({
children: true,
})
【額外的異步公共chunk】

復制代碼
new webpack.optimize.CommonsChunkPlugin({
name: "app",
// or
names: ["app", "subPageA"]
children: true,
async: true,
minChunks: 3,
})
復制代碼
【wepack4】

  webpack 4 將移除 CommonsChunkPlugin, 取而代之的是兩個新的配置項 optimization.splitChunks 和 optimization.runtimeChunk

  通過設置 optimization.splitChunks.chunks: "all" 來啟動默認的代碼分割配置項

  當滿足如下條件時,webpack 會自動打包 chunks:

當前模塊是公共模塊(多處引用)或者模塊來自 node_modules
當前模塊大小大於 30kb
如果此模塊是按需加載,並行請求的最大數量小於等於 5
如果此模塊在初始頁面加載,並行請求的最大數量小於等於 3
  通過設置 optimization.runtimeChunk: true 來為每一個入口默認添加一個只包含 runtime 的 chunk

動態導入
  上面介紹的CommonsChunkPlugin可以去重和分離chunk。而本節介紹的動態導入,則是通過模塊的內聯函數調用來分離代碼

  webpack 提供了兩個類似的技術。對於動態導入,第一種,也是優先選擇的方式是,使用符合 ECMAScript 提案 的 import() 語法。第二種,則是使用 webpack 特定的 require.ensure

  下面來使用import()語法來進行動態導入

復制代碼
const path = require('path');
const HTMLWebpackPlugin = require('html-webpack-plugin');

module.exports = {
entry: {
index: './src/index.js'
},
plugins: [
new HTMLWebpackPlugin({
title: 'Code Splitting'
})
],
output: {
filename: '[name].bundle.js',
chunkFilename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist')
}
};
復制代碼
  下面來動態導入loadsh

復制代碼
function getComponent() {
 return import(/* webpackChunkName: "lodash" */ 'lodash').then(_ => {
var element = document.createElement('div');
element.innerHTML = _.join(['Hello', 'webpack'], ' ');
return element;
}).catch(error => 'An error occurred while loading the component');
} getComponent().then(component => {
document.body.appendChild(component);
})
復制代碼
  在注釋中使用了 webpackChunkName。這樣做會導致bundle 被命名為 lodash.bundle.js ,而不是 [id].bundle.js

懶加載
  懶加載或者按需加載,是一種很好的優化網頁或應用的方式。這種方式實際上是先把你的代碼在一些邏輯斷點處分離開,然后在一些代碼塊中完成某些操作后,立即引用或即將引用另外一些新的代碼塊。這樣加快了應用的初始加載速度,減輕了它的總體體積,因為某些代碼塊可能永遠不會被加載

  上面通過動態導入的loadsh確實會在腳本運行的時候產生一個分離的代碼塊 lodash.bundle.js ,在技術概念上“懶加載”它。問題是加載這個包並不需要用戶的交互 -- 意思是每次加載頁面的時候都會請求它

  下面來增加一個交互,當用戶點擊按鈕的時候用 console 打印一些文字。但是會等到第一次交互的時候再加載那個代碼塊(print.js)

//print.js
console.log('The print.js module has loaded! See the network tab in dev tools...');
export default () => {
console.log('Button Clicked: Here's "some text"!');
}
復制代碼
//index.js
import _ from 'lodash';
function component() {
var element = document.createElement('div');
var button = document.createElement('button');
var br = document.createElement('br');
button.innerHTML = 'Click me and look at the console!';
element.innerHTML = _.join(['Hello', 'webpack'], ' ');
element.appendChild(br);
element.appendChild(button);
button.onclick = e => import(/* webpackChunkName: "print" */ './print').then(module => {
var print = module.default;
print();
});
return element;
}
document.body.appendChild(component());
復制代碼

剔除無用代碼
  tree shaking 是一個術語,通常用於描述移除 JavaScript 上下文中的未引用代碼(dead-code)。它依賴於 ES2015 模塊系統中的靜態結構特性,例如 import 和 export。這個術語和概念實際上是興起於 ES2015 模塊打包工具 rollup

【JS】

  JS的tree shaking主要通過uglifyjs插件來完成

npm install --save-dev uglifyjs-webpack-plugin
復制代碼
const path = require('path');
const UglifyJSPlugin = require('uglifyjs-webpack-plugin');

module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
plugins: [
new UglifyJSPlugin()
]
};
復制代碼
【CSS】

  CSS的tree shaking主要通過purify CSS來實現的

npm i -D purifycss-webpack purify-css
復制代碼
const path = require('path');
const glob = require('glob');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const PurifyCSSPlugin = require('purifycss-webpack');

module.exports = {
entry: {...},
output: {...},
module: {
rules: [
{
test: /.css$/,
loader: ExtractTextPlugin.extract({
fallbackLoader: 'style-loader',
loader: 'css-loader'
})
}
]
},
plugins: [
new ExtractTextPlugin('[name].[contenthash].css'),
// Make sure this is after ExtractTextPlugin!
new PurifyCSSPlugin({
// Give paths to parse for rules. These should be absolute!
paths: glob.sync(path.join(__dirname, 'app/*.html')),
})
]
};
復制代碼
  如果要設置多路徑,則需要將glob換成glob-all

const glob = require('glob-all');
paths: glob.sync([
path.join(__dirname, '.php'),
path.join(__dirname, 'partials/.php')
])

模塊熱更新
  模塊熱更新,又稱為模塊熱替換,HMR(Hot Module Replacement)。在應用程序運行過程中替換、添加或刪除模塊,無需重新加載頁面,極大地加速了開發時間

  啟用此功能實際上相當簡單。而我們要做的,就是更新 webpack-dev-server 的配置,和使用 webpack 內置的 HMR 插件。此外,還添加了NamedModulesPlugin,以便更容易查看要修補(patch)的依賴

復制代碼
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const webpack = require('webpack');

module.exports = {
entry: {
app: './src/index.js'
},
devtool: 'inline-source-map',
devServer: {
contentBase: './dist',
hot: true
},
plugins: [
new CleanWebpackPlugin(['dist']),
new HtmlWebpackPlugin({
title: 'Hot Module Replacement'
}),
new webpack.NamedModulesPlugin(),
new webpack.HotModuleReplacementPlugin()
],
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist')
}
};
復制代碼

長緩存優化
【使用chunkhash】

  將hash替換為chunkhash,這樣當chunk不變時,緩存依然有效

復制代碼
const path = require('path');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
entry: './src/index.js',
plugins: [
new CleanWebpackPlugin(['dist']),
new HtmlWebpackPlugin({
title: 'Caching'
})
],
output: {
filename: '[name].[chunkhash].js',
path: path.resolve(__dirname, 'dist')
}
};
復制代碼
【提取模板】

  CommonsChunkPlugin 可以用於將模塊分離到單獨的文件中。還能夠在每次修改后的構建結果中,將 webpack 的樣板(boilerplate)和 manifest 提取出來。通過指定 entry 配置中未用到的名稱,此插件會自動將我們需要的內容提取到單獨的包中

復制代碼
const 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',
plugins: [
new CleanWebpackPlugin(['dist']),
new HtmlWebpackPlugin({
title: 'Caching'
}),
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest'
})
],
output: {
filename: '[name].[chunkhash].js',
path: path.resolve(__dirname, 'dist')
}
};
復制代碼
  將第三方庫(library)(例如 lodash 或 react)提取到單獨的 vendor chunk 文件中,是比較推薦的做法,這是因為,它們很少像本地的源代碼那樣頻繁修改。因此通過實現以上步驟,利用客戶端的長效緩存機制,可以通過命中緩存來消除請求,並減少向服務器獲取資源,同時還能保證客戶端代碼和服務器端代碼版本一致。這可以通過使用新的 entry(入口) 起點,以及再額外配置一個 CommonsChunkPlugin 實例的組合方式來實現

復制代碼
var path = require('path');
const webpack = require('webpack');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
entry: {
main: './src/index.js',
vendor: [
'lodash'
]
},
plugins: [
new CleanWebpackPlugin(['dist']),
new HtmlWebpackPlugin({
title: 'Caching'
}),
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor'
}),
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest'
})
],
output: {
filename: '[name].[chunkhash].js',
path: path.resolve(__dirname, 'dist')
}
};
復制代碼
  [注意]CommonsChunkPlugin 的 'vendor' 實例,必須在 'manifest' 實例之前引入

【使用Name而不是id】

  每個 module.id 會基於默認的解析順序(resolve order)進行增量。也就是說,當解析順序發生變化,ID 也會隨之改變

  下面來使用兩個插件解決這個問題。第一個插件是 NamedModulesPlugin,將使用模塊的路徑,而不是數字標識符。雖然此插件有助於在開發過程中輸出結果的可讀性,然而執行時間會長一些。第二個選擇是使用 HashedModuleIdsPlugin,推薦用於生產環境構建

復制代碼
const path = require('path');
const webpack = require('webpack');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
entry: {
main: './src/index.js',
vendor: [
'lodash'
]
},
plugins: [
new CleanWebpackPlugin(['dist']),
new HtmlWebpackPlugin({
title: 'Caching'
}),
new webpack.HashedModuleIdsPlugin(),
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor'
}),
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest'
})
],
output: {
filename: '[name].[chunkhash].js',
path: path.resolve(__dirname, 'dist')
}
};
復制代碼

公用代碼內聯
  使用html-webpack-inline-chunk-plugin插件將mainfest.js內聯到html文件中

$ npm install html-webpack-inline-chunk-plugin --save-dev
復制代碼
const path = require('path');
const webpack = require('webpack');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const InlineChunkWebpackPlugin = require('html-webpack-inline-chunk-plugin');

module.exports = {
entry: {
main: './src/index.js',
vendor: [
'lodash'
]
},
plugins: [
new CleanWebpackPlugin(['dist']),
new HtmlWebpackPlugin({
title: 'Caching',
inlineSource: '.(js|css)$'
}),
new webpack.HashedModuleIdsPlugin(),
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor'
}),
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest'
}),
new InlineChunkWebpackPlugin({
inlineChunks: ['manifest']
})
],
output: {
filename: '[name].[chunkhash].js',
path: path.resolve(__dirname, 'dist')
}
};


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM