Webpack極限打包優(yōu)化

今天為了更好地了解一下Webpack打包優(yōu)化的一些內(nèi)容,看了一下NEXT公開課,Webpack打包極限優(yōu)化,感興趣的朋友可以去騰訊課堂看看,我這里也是對于公開課的筆記總結(jié)!

其中講到的點(diǎn)如下所示:

  • WebPack基礎(chǔ)知識

  • 構(gòu)建速度優(yōu)化

  • 案例優(yōu)化效果對比

  • 如何分析頁面打包問題?

  • 構(gòu)建體積優(yōu)化

  • 總結(jié)和展望

01|為什么需要構(gòu)建工具?
  • 轉(zhuǎn)換ES6語法
  • 轉(zhuǎn)換JSX
  • CSS前綴補(bǔ)全/預(yù)處理器
  • 壓縮混淆
  • 圖片壓縮

其中對應(yīng)的瀏覽器的支持情況,涉及到了 CANIUSE中的兼容處理!

我們可以通過最基本的例子來演示:

  • WebPack腳本
const path = require("path");
module.exports = {
    mode:'production',
    entry:'./src/index.js',
    output:{
        path:path.resolve(__dirname,'dist'),
        filename:'bundle.js'
    }
}
  • 構(gòu)建結(jié)果
<!DOCTYPE html>
<html>
    <head>
        <title>example</title>
    </head>
    <body>
       <script src="dist/bundle.js"></script>   
    </body>
</html>

其實(shí)通過Webpack的腳本很好看出具體做了什么?

mode就是制定腳本的運(yùn)行環(huán)境,對應(yīng)的entry就是表示的入口文件,output則是指定編譯的目錄和文件!

執(zhí)行流程

Entry                                                                       Output
    app/index.js                Webpack/Plugins/Loaders/                  dist/app.js 
        |                           對應(yīng)的loader進(jìn)行處理                         | 
        |                                                                     | 
        |                                                           Split dist/0.js 
  app/component.js
        |
        |
     app/util.js

對應(yīng)的定位就是模塊打包器!

Entry對應(yīng)的入口,根據(jù)對應(yīng)的entry就可以知道對應(yīng)的依賴樹,比如說 index.js依賴 component.js component.js依賴util.js

對應(yīng)的Plugin和Loader有什么區(qū)別嗎?

  • Loader主要是做資源的解析操作
  • Plugin更加強(qiáng)大,為Webpack的拓展,可以做很多l(xiāng)oader沒法做的能夠增強(qiáng)Webpack的功能!

目標(biāo)代碼:

  • dist文件中
  • 為了更好的加載會將文件做分割以便更好地做懶加載!
02|Grunt,Gulp和WebPacl的對比
  • Task runner

Grunt處理SASS轉(zhuǎn)換成為CSS的過程

run('sass') source => **/**.sass => SASS **/*.css* => .tmp/

run('autoprefixer') .tmp/ => **/*.css* => Auto-prefixer => **/*.css* dest

對應(yīng)的Task Runner表示任務(wù)運(yùn)行器的意思!

但是對于對應(yīng)的Gulp來講的話,它是以流式的Task Runner起作用的!

source ==> **/*.sass ==> SASS ==> **/*.css ==> Auto-prefixer ==> **/*.css ==> desc

相較于Grunt來看的話,沒有對應(yīng)的temp文件夾,對應(yīng)的數(shù)據(jù)是存放在內(nèi)存中的,與此同時(shí)對應(yīng)的任務(wù)是 流式構(gòu)建

  • 那么問題來了,對應(yīng)的Webpack的區(qū)別是什么?

在Webpack中對于資源的處理又是如何做的呢? 將對應(yīng)的資源都是當(dāng)成模塊來處理!

通過對應(yīng)的entry來索引模塊的依賴樹之后傳遞給Webpack的引擎,加載對應(yīng)的loader文件解析成對應(yīng)的CSS JS文件!

03|初級分析-使用Webpack內(nèi)置的stats
  • stats:構(gòu)建的統(tǒng)計(jì)信息
  • package.json中使用stats
"scripts":{
    "build:stats":"webpack --env production --json > stats.json"
}    
  • Node API中使用
const wbepack = require("webpack");
const config = require("./webpack.config.js")("production");
webpack(config,(error,stats)=>{
    if(error){
       return console.error(error);
    }
    if(stats.hasError){
       return console.error(stats.toString("errors-only"));
    }
    console.log(stats);
})

粒度太粗,看不出問題所在!

如果說是對應(yīng)的速度分析-使用speed-measure-webpack-plugin

  • 代碼示例
const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");
const smp = new SpeedMeasurePlugin();
const webpackConfig = smp.wrap({
    plugins:{
        new MyPlugin(),
    new MyOtherPlugin()
    }
})

對應(yīng)的可以看到每個(gè)Loader和插件執(zhí)行耗時(shí)!

體積分析-使用webpack-bundle-analyzer

  • 代碼示例:
const BundleAnalyzerPlugin = require("webpack-bundle-analyzer").BundleAnalyzerPlugin;
module.exports = {
    plugins:{
        new BundleAnalyzerPlugin()
    }
}

構(gòu)建完成之后會在8888端口展示大小!

對應(yīng)的速度優(yōu)化策略

  • 使用Webpack4
  • 多進(jìn)程/多實(shí)例構(gòu)建
  • 分包
  • 緩存
  • 縮小構(gòu)建目標(biāo)
  1. 使用新的Webpack帶來的受益確實(shí)不錯(cuò),那么使用Webpack4究竟好在哪里,為什么要使用Webpack4?
  • V8帶來的優(yōu)化(for or替代forEach,Map和Set替代Object,includes替代indexOf)
  • 默認(rèn)使用更快的md4 hash算法
  • webpacks AST直接從loader傳遞給AST,減少了解析時(shí)間
  • 使用字符串方法替代正則表達(dá)式
  1. 多進(jìn)程/多實(shí)例構(gòu)建-資源并行解析可選方案

thread-loader 可選方案

  • paraller-webpack
  • HappyPack

多進(jìn)程/多實(shí)例-使用HappyPack解析資源

  • 原理:每次Webpack解析一個(gè)模塊,HappyPack會將它以及他的依賴分配給worker線程中

  • 代碼示例:

export.plugins = {
    new HappyPack({
    id:"jsx",
    threads:4,
    loaders:['babel-loader']
    }),
    new HappyPack({
        id:"styles",
        threads:2,
        loaders:['style-loader','css-loader','less-loader']
    })        
}

對應(yīng)的工作流程:

HappyThreadPool
       ^
       |
       |
  HappyPlugin
       ^
       |
       |
   HappyLoader
       ^ 
       |
       | 
    Webpack 

中間部分:

HappyThread[1,N]

右邊部分:

HappyWorkerCHannel[1,N]
        |
        |
    HappyWork[1,N]
        |
        |
    webpack.loader

多進(jìn)程/多實(shí)例-并行壓縮

  • 方法一:使用parallel-uglify-plugin插件
const ParallelUglifyPlugin = require("webpack-parallel-uglify-plugin");
module.exports = {
    plugins:[
        new ParallelUglifyPlugin({
            uglifyJS:{
                output:{
                    beautify:false,
                    comments:false
                },
                compress:{
                    warnings:false,
                    drop_console:true,
                    collapse_vars:true,
                    reduce_vars:true
                }
            }   
        })
    ]
}
  • 方法二:uglifyjs-webpack-plugin開啟parallel參數(shù)
const UglifyJSPlugin = require("uglifyjs-webpack-plugin");
module.exports = {
    plugins:[
        new UglifyJsPlugin({
            uglifyOptions:{
                warnings:false,
                parse:{},
                compress:{},
                mangle:true,
                output:null,
                toplevel:false,
                nameCache:null,
                ie8:false,
                keep_fnames:false
            },
            parallel:true
        })
    ]   
}

分包-設(shè)置Externals

  • 思路:將React,react-dom基礎(chǔ)包通過cdn引入,不打入bundle中

  • 方法:使用html-webpack-externals-plugin

const HtmlWebpackExternalsPlugin = require("html-webpack-externals-plugin");
plugins:[
    new HtmlWebpackExternalsPlugin({
        externals:[
            {
                module:'react',
                entry:'//11.url.cn/now/lib/15.1.0/react-with-addons.min.js?_bid=3123',
                global:'React'
            },{
                module:'react-dom',
                entry:'//11.url.cn/now/lib/15.1.0/react-dom.min.js?_bid=3123',
                global:'ReactDOM'
            }
        ]
    })
]

進(jìn)一步分包-預(yù)編譯資源模塊

  • 思路:將React,react-dom,redux,react-redux基礎(chǔ)包和業(yè)務(wù)基礎(chǔ)打包成為一個(gè)文件
  • 方法:使用DLLPlugin進(jìn)行分包,DllReferencePlugin對manifest.json引用
const path = require("path");
const webpack = require("webpack");
module.exports={
    context:process.cwd(),
    resolve:{
        extensions:['.js','.jsx','.json','.less','.css'],
        modules:[__dirname,'node_modules']
    },entey:{
        library:[
            'react','react-dom','redux','react-redux'
        ]
    },
    output:{
        filename:'[name].dll.js',
        path:path.resolve(__dirname,'./build/library'),
        library:'[name]'
    },plugins:[
        new webpack.DLLPlugin({
            name:'[name]',
            path:'./build/library/[name].json'
        })
    ]
}

緩存

  • 目的:提升二次構(gòu)建速度
  • 方法:使用HardSourceWEbpackPlugin或者cache-loader
module.exports = {
    plugins:new HardSourceWebpackPlugin({
        cacheDirectory:'node_modules/.cache/hard-source/[confighash]',
        configHash:function(webpackConfig){
            return require('node-object-hash')({sort:false}).hash(webpackCOnfig);
        },
        environmentHash:{
            root:process.cwd(),
            directories:[],
            files:['package-lock.json','yarn.lock']
        },
        info:{
            mode:'none',level:"debug"
        },cachePrune:{
            maxAge:2*24*60*60*1000,
            sizeThreshold:50*1024*1024
        }
    })
}

縮小構(gòu)建的目標(biāo):

  • 目的:盡可能的少模塊構(gòu)建
  • 比如:babel-loader不解析node_modules
module.exports = {
    rules:{
        text:/\.js$/,
        loader:'happypack/loader',
        exclude:'node_modules'
    }
}

體積優(yōu)化策略:

  • Scope Hoisting
  • Tree-shaking
  • 公共資源分離
  • 圖片壓縮
  • 動態(tài)Polyfill
  1. Scope-Hoisting原理

原理:將所有模塊的代碼按照引用順序放在一個(gè)函數(shù)作用域中,然后適當(dāng)?shù)闹孛恍┳兞糠乐棺兞棵麤_突

對比:通過scope hoisting可以減少函數(shù)聲明代表

示例代碼:

module.exports = {
    plugins:[
        new webpack.optimize.ModuleConcatenationPlugin()
    ]
}
  • 對應(yīng)的要求必須是ES6的語法,CJS的方式不支持

Tree-shaking

  • 概念:1個(gè)模塊可能有多個(gè)方法,只要其中的某個(gè)方法使用到了,則整個(gè)文件都會被打倒bundle里面去,Tree-shaking就是指把用到的方法打入bundle,沒用到的方法會在uglify階段被擦除掉!
  • 使用:webpack默認(rèn)支持,在.babelrc里面設(shè)置modules:false即可
  • 要求:必須是ES6的語法,CJS的方式不支持

公共資源分離

  • 目的:提取多頁面公共JS chunk代碼
  • 使用
  • webpack3使用commonsChunkPlugin
  • webpack4使用SplitChunksPlugin
module.exports = {
    optimization:{
        splitChunks:{
            chunks:'async',
            minSize:30000,
            maxSize:0,
            minChunks:1,
            maxAsyncRequests:5,
            maxInitialRequests:3,
            automaticNameDelimiter:'~',
            name:true,
            cacheGroups:{
                vendors:{
                    test:/[\\/\]node_modules[\\/]/,
                    priority:-10
                },default:{
                    minChunks:2,
                    priority:-20,
                    reuseExistingChunk:true
                }
            }
        }
    }
}

圖片壓縮:

  • 要求:基于Node庫的imagemin或者tinypng API
  • 使用:配置image-webpack-loader
return {
    test:/\.{png|svg|jpg|gif|blob}$/,
    yse:[{
        loader:'file-loader',
        options:{
            name:`${filename}img/[name]${hash}.[ext]`
        }
    },{
        loader:'image-webpack-loader',
        options:{
            mozjpeg:{
                progressive:true,
                quality:65
            },optipng:{
                enabled:false
            },pngquant:{
                quality:'65-90',
                speed:4
            },gifsicle:{
                interlaced:false
            },webp:{
                quality:75
            }
        }
    }]
}

構(gòu)建體積優(yōu)化-動態(tài)Polyfill

babel-polufill

方案 優(yōu)點(diǎn) 缺點(diǎn) 是否采用
babel-polyfill 官方推薦 單獨(dú)構(gòu)建 react前加載 包體積比較大 ×
babel-plugin-transform-runtime 只能用到類或者方法 對體積較小 不適用于復(fù)雜的開發(fā)環(huán)境 ×
map,set的polyfill 定制化體積小 重復(fù)造輪子 體積小染有用的都加載 ×
polyfill-service 只給用戶需要的 社區(qū)和維護(hù) 部分國內(nèi)瀏覽器可能無法識別 但是可以降級返回所有的polyfill

如何動態(tài)使用Polyfill service?

  • 使用polufill.io官方提供的服務(wù)
  • 基于官方自建的polyfill的服務(wù)

總結(jié)和展望:

  • 遇到打包速度和體積問題如何優(yōu)化
  • 體積優(yōu)化的策略
  • 速度優(yōu)化的策略
  • 弄清楚基本原理比較重要
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • webpack 是什么? 本質(zhì)上,webpack 是一個(gè)現(xiàn)代 JavaScript 應(yīng)用程序的靜態(tài)模塊打包器(mo...
    IT老馬閱讀 3,567評論 2 27
  • 目錄第1章 webpack簡介 11.1 webpack是什么? 11.2 官網(wǎng)地址 21.3 為什么使用 web...
    lemonzoey閱讀 1,832評論 0 1
  • section-2.1 什么是loader loader 用于對模塊的源代碼進(jìn)行轉(zhuǎn)換。loader 可以使你在 i...
    羽晞yose閱讀 679評論 0 0
  • 基礎(chǔ) 從webpack文件上來看,主要用到的有entry,output,resolve,module,plugin...
    2林子易2閱讀 2,316評論 2 3
  • 公司在應(yīng)對國際金融危機(jī)時(shí): 第一,樹立“應(yīng)機(jī)”的戰(zhàn)略思維。在金融危機(jī)中及時(shí)調(diào)整產(chǎn)品結(jié)構(gòu),適應(yīng)國際需求的變化,不斷開...
    檐牙燕雀閱讀 150評論 0 0

友情鏈接更多精彩內(nèi)容