webpack从入门到精通(四)优化打包配置总结②
1. tree shaking
tree-shaking的本質是消除無用的js代碼。無用代碼消除廣泛存在于傳統的編程語言編譯器中,編譯器可以判斷出某些代碼根本不影響輸出,然后消除這些代碼,這個稱之為DCE(dead code elimination)。
Tree-shaking 是 DCE 的一種新的實現,Javascript同傳統的編程語言不同的是,javascript絕大多數情況需要通過網絡進行加載,然后執行,加載的文件大小越小,整體執行時間更短,所以去除無用代碼以減少文件體積,對javascript來說更有意義。
它依賴于 ES2015 模塊系統中的靜態結構特性,例如 import 和 export。這個術語和概念實際上是興起于 ES2015 模塊打包工具 rollup。
新的 webpack 4 正式版本,擴展了這個檢測能力,通過 package.json 的 "sideEffects" 屬性作為標記,向 compiler 提供提示,表明項目中的哪些文件是 "pure(純的 ES2015 模塊)",由此可以安全地刪除文件中未使用的部分。
在一個純粹的 ESM 模塊世界中,識別出哪些文件有副作用很簡單。然而,我們的項目無法達到這種純度,所以,此時有必要向 webpack 的 compiler 提供提示哪些代碼是“純粹部分”。
如果所有代碼都不包含副作用,我們就可以簡單地將該屬性標記為 false,來告知 webpack,它可以安全地刪除未用到的 export 導出。
如果你的代碼確實有一些副作用,那么可以改為提供一個數組。
前提:1. 必須使用ES6模塊化 2. 開啟production環境 ? 在package.json中配置 "sideEffects": ["*.css", "*.less"]index.js
import { mul } from './test'; import '../css/index.css'; ? function sum(...args) {return args.reduce((p, c) => p + c, 0); } ? // eslint-disable-next-line console.log(mul(2, 3)); // eslint-disable-next-line console.log(sum(1, 2, 3, 4));test.js
export function mul(x, y) {return x * y; } ? export function count(x, y) {return x - y; }可以發現,count()函數我們并沒有使用。打包后的文件中將不會包含count()函數。
2. code split
何為Code Split?
webpack從入口文件開始遍歷,找到所有依賴文件,然后打包成最終的一個文件,即bundle.js文件,這是我們經常使用的方式,當一個項目慢慢變得復雜的時候會導致這個bundle.js文件越來越大,瀏覽器加載的速度也會越來越慢,這個過程還不排除我們需要引用的第三方文件,這樣每次無論是構建,還是瀏覽器加載這個最終文件,都會存在效率問題,webpack提供了codesplitting功能來解決這個問題,這可以最大限度的減少瀏覽器加載必要代碼時間(比如首屏渲染優化)。這個過程我們可以分為兩種情況來討論,第三方的分為靜態文件處理,瀏覽器加載必要文件作為動態文件處理(按需加載,懶加載)
常見的 webpack code split 方法有三種。
1)多入口配置
?// 單入口// entry: './src/js/index.js',entry: {// 多入口:有一個入口,最終輸出就有一個bundleindex: './src/js/index.js',test: './src/js/test.js'}2)使用plugins配置進行分割
webpack 在4.0版本開始對防重復方式進行了改寫,通過配置optimization。
-
可以將node_modules中代碼單獨打包一個chunk最終輸出
-
自動分析多入口chunk中,有沒有公共的文件。如果有會打包成單獨一個chunk
我們下面演示一下:
我們在index.js和test.js中都引入第三方庫jquery
index.js
import $ from 'jquery'; import { mul } from './test'; function sum(...args) {return args.reduce((p, c) => p + c, 0); } ? ? console.log(sum(1, 2, 3, 4)); console.log($);; console.log(mul(2,3));test.js
import $ from 'jquery'; ? // eslint-disable-next-line console.log($); ? export function mul(x, y) {return x * y; } ? export function count(x, y) {return x - y; }單入口配置webpack.config.js
const { resolve } = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); ? module.exports = {// 單入口entry: './src/js/index.js',output: {// [name]:取文件名filename: 'js/[name].[contenthash:10].js',path: resolve(__dirname, 'build')},plugins: [new HtmlWebpackPlugin({template: './src/index.html',minify: {collapseWhitespace: true,removeComments: true}})],/*1. 可以將node_modules中代碼單獨打包一個chunk最終輸出2. 自動分析多入口chunk中,有沒有公共的文件。如果有會打包成單獨一個chunk*/optimization: {splitChunks: {chunks: 'all'}},mode: 'production' };打包出的文件如下:
jquery被單獨打成一個文件。
注意:如果沒有引入optimization插件,則只會生成main.js一個文件,且文件大小會比較大。
多入口配置webpack.config.js
?entry: {index: './src/js/index.js',test: './src/js/test.js'}打包出的文件如下:
index.js和test.js中引入了同樣的jquery,被合并打包成同一個文件。
3)動態導入
上面我們發現,在單入口配置中,index.js中引入了test.js,但是test.js并不會單獨打包,我們可以通過import來動態導入。
語法如下:
// 可以通過注釋來設置文件名 import(/* webpackChunkName: "home" */ "url文件路徑").then((text)=> {// do something })index.js
function sum(...args) {return args.reduce((p, c) => p + c, 0); } ? /*通過js代碼,讓某個文件被單獨打包成一個chunkimport動態導入語法:能將某個文件單獨打包可以通過注釋來設置文件名 */ import(/* webpackChunkName: 'test' */'./test').then(({ mul, count }) => {// 文件加載成功~// eslint-disable-next-lineconsole.log(mul(2, 5));}).catch(() => {// eslint-disable-next-lineconsole.log('文件加載失敗~');}); ? // eslint-disable-next-line console.log(sum(1, 2, 3, 4)); ?打包出的文件如下:
可以發現,test.js和main.js分別打包成了兩個文件。
3. 懶加載和預加載
懶加載或者按需加載,是一種很好的優化網頁或應用的方式。這種方式實際上是先把你的代碼在一些邏輯斷點處分離開,然后在一些代碼塊中完成某些操作后,立即引用或即將引用另外一些新的代碼塊。這樣加快了應用的初始加載速度,減輕了它的總體體積,因為某些代碼塊可能永遠不會被加載。
通俗來說就是,如果每次加載頁面的時候都會加載某些代碼塊,會重復的請求,造成資源的浪費,影響網站性能。所以提出了懶加載的解決辦法:按需加載,初始化不需要加載此代碼塊,等到具體的執行需要時候,再加載,從而優化性能。
例如:在點擊按鈕的事件處理中,才需要用到來自print.js的print函數。所以可以將代碼寫成點擊時候觸發加載print.js。
正常加載:
即使沒有點擊,test.js文件也會直接加載。
console.log('index.js文件被加載了~'); ? import { mul } from './test'; ? ? document.getElementById('btn').onclick = function() {console.log(mul(4, 5)); };懶加載:文件需要使用時才加載
只有點擊后,test.js文件才會加載。
console.log('index.js文件被加載了~'); ? document.getElementById('btn').onclick = function() { ?import(/* webpackChunkName: 'test' */'./test').then(({ mul }) => {console.log(mul(4, 5));}); };點擊前的資源加載如下:
預加載 prefetch:會在使用之前,提前加載 js 文件,等其他資源加載完畢,瀏覽器空閑了,偷偷加載資源
console.log('index.js文件被加載了~'); ? document.getElementById('btn').onclick = function() {import(/* webpackChunkName: 'test', webpackPrefetch: true */'./test').then(({ mul }) => {console.log(mul(4, 5));}); };預加載的效果和懶加載是一樣的,不過資源文件加載方式如下:
可以發現,雖然沒使用test.js,但會提前加載資源
4. 多進程打包
1)下載plugin包
npm install --save-dev thread-loader2)分析
進程啟動大概為 600ms,進程通信也有開銷,當只有工作消耗時間比較長時,才需要多進程打包
3)配置文件
{test: /\.js$/,use: [{loader: 'thread-loader',options: {workers: 2 // 進程2個}}] }5. externals
webpack 中的 externals 配置提供了不從 bundle 中引用依賴的方式。解決的是,所創建的 bundle 依賴于那些存在于用戶環境(consumer environment)中的依賴。
怎么理解呢,意思是如果需要引用一個庫,但是又不想讓webpack打包(減少打包的時間),并且又不影響我們在程序中以CMD、AMD或者window/global全局等方式進行使用(一般都以import方式引用使用),那就可以通過配置externals。
這樣做的目的就是將不怎么需要更新的第三方庫脫離webpack打包,不被打入bundle中,從而減少打包時間,但又不影響運用第三方庫的方式,例如import方式等。
例如:
在index.html中引入jquery庫
?<script src="https://cdn.bootcss.com/jquery/1.12.4/jquery.min.js"></script>webpack.config.js配置如下:
const { resolve } = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); ? module.exports = {entry: './src/js/index.js',output: {filename: 'js/built.js',path: resolve(__dirname, 'build')},plugins: [new HtmlWebpackPlugin({template: './src/index.html'})],mode: 'production',externals: {// 拒絕jQuery被打包進來jquery: 'jQuery'} }; ?index.js
import $ from 'jquery'; ? console.log($);排除之后,我們的jquery并沒有打包進index.js中。
這樣不僅之前對第三方庫的用法方式不變,還把第三方庫剝離出webpack的打包中,從而加速webpack的打包速度。
6. dll
事先把常用但又構建時間長的代碼提前打包好(例如 react、react-dom),取個名字叫 dll。后面再打包的時候就跳過原來的未打包代碼,直接用 dll。這樣一來,構建時間就會縮短,提高 webpack 打包速度。
使用dll時,可以把構建過程分成dll構建過程和主構建過程(實質也就是如此),所以需要兩個構建配置文件,例如叫做webpack.config.js和webpack.dll.config.js。
1)使用DLLPlugin打包需要分離到動態庫的模塊
DllPlugin是webpack內置的插件,不需要額外安裝,直接配置webpack.dll.config.js文件:
/*使用dll技術,對某些庫(第三方庫:jquery、react、vue...)進行單獨打包當你運行 webpack 時,默認查找 webpack.config.js 配置文件需求:需要運行 webpack.dll.js 文件--> webpack --config webpack.dll.config.js */ ? const { resolve } = require('path'); const webpack = require('webpack'); ? module.exports = {entry: {// 最終打包生成的[name] --> jquery// ['jquery'] --> 要打包的庫是jqueryjquery: ['jquery']},output: {filename: '[name].js',path: resolve(__dirname, 'dll'),library: '[name]_[hash]' // 打包的庫里面向外暴露出去的內容叫什么名字},plugins: [// 打包生成一個 manifest.json --> 提供和jquery映射new webpack.DllPlugin({name: '[name]_[hash]', // 映射庫的暴露的內容名稱path: resolve(__dirname, 'dll/manifest.json') // 輸出文件路徑})],mode: 'production' }; ?執行:webpack --config webpack.dll.config,然后到指定的輸出文件夾查看輸出:
jquery.js文件
manifest.json文件里,是用來描述對應的dll文件里保存的模塊里暴露出剛剛構建的所有模塊
2)在主構建配置文件使用動態庫文件
在webpack.config中使用dll要用到DllReferencePlugin,這個插件通過引用 dll 的 manifest 文件來把依賴的名稱映射到模塊的 id 上。
下面的配置文件還使用了dd-asset-html-webpack-plugin,會讓我們的index.html自動引入該資源。
const { resolve } = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const webpack = require('webpack'); const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin'); ? module.exports = {entry: './src/index.js',output: {filename: 'built.js',path: resolve(__dirname, 'build')},plugins: [new HtmlWebpackPlugin({template: './src/index.html'}),// 告訴webpack哪些庫不參與打包,同時使用時的名稱也得變~new webpack.DllReferencePlugin({manifest: resolve(__dirname, 'dll/manifest.json')}),// 將某個文件打包輸出去,并在html中自動引入該資源new AddAssetHtmlWebpackPlugin({filepath: resolve(__dirname, 'dll/jquery.js')})],mode: 'production' };打包前的index.html
?<h1 id="title">hello html</h1>打包后的index.html
?<h1 id="title">hello html</h1> <script type="text/javascript" src="jquery.js"></script><script type="text/javascript" src="built.js"></script></body>?
總結
以上是生活随笔為你收集整理的webpack从入门到精通(四)优化打包配置总结②的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: webpack从入门到精通(四)优化打包
- 下一篇: docker安装elasticsearc