美文网首页
Webpack极限打包优化

Webpack极限打包优化

作者: 井润 | 来源:发表于2019-11-23 22:07 被阅读0次

    今天为了更好地了解一下Webpack打包优化的一些内容,看了一下NEXT公开课,Webpack打包极限优化,感兴趣的朋友可以去腾讯课堂看看,我这里也是对于公开课的笔记总结!

    其中讲到的点如下所示:

    • WebPack基础知识

    • 构建速度优化

    • 案例优化效果对比

    • 如何分析页面打包问题?

    • 构建体积优化

    • 总结和展望

    01|为什么需要构建工具?
    • 转换ES6语法
    • 转换JSX
    • CSS前缀补全/预处理器
    • 压缩混淆
    • 图片压缩

    其中对应的浏览器的支持情况,涉及到了 CANIUSE中的兼容处理!

    我们可以通过最基本的例子来演示:

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

    其实通过Webpack的脚本很好看出具体做了什么?

    mode就是制定脚本的运行环境,对应的entry就是表示的入口文件,output则是指定编译的目录和文件!

    执行流程

    Entry                                                                       Output
        app/index.js                Webpack/Plugins/Loaders/                  dist/app.js 
            |                           对应的loader进行处理                         | 
            |                                                                     | 
            |                                                           Split dist/0.js 
      app/component.js
            |
            |
         app/util.js
    

    对应的定位就是模块打包器!

    Entry对应的入口,根据对应的entry就可以知道对应的依赖树,比如说 index.js依赖 component.js component.js依赖util.js

    对应的Plugin和Loader有什么区别吗?

    • Loader主要是做资源的解析操作
    • Plugin更加强大,为Webpack的拓展,可以做很多loader没法做的能够增强Webpack的功能!

    目标代码:

    • dist文件中
    • 为了更好的加载会将文件做分割以便更好地做懒加载!
    02|Grunt,Gulp和WebPacl的对比
    • Task runner

    Grunt处理SASS转换成为CSS的过程

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

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

    对应的Task Runner表示任务运行器的意思!

    但是对于对应的Gulp来讲的话,它是以流式的Task Runner起作用的!

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

    相较于Grunt来看的话,没有对应的temp文件夹,对应的数据是存放在内存中的,与此同时对应的任务是 流式构建

    • 那么问题来了,对应的Webpack的区别是什么?

    在Webpack中对于资源的处理又是如何做的呢? 将对应的资源都是当成模块来处理!

    通过对应的entry来索引模块的依赖树之后传递给Webpack的引擎,加载对应的loader文件解析成对应的CSS JS文件!

    03|初级分析-使用Webpack内置的stats
    • stats:构建的统计信息
    • 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);
    })
    

    粒度太粗,看不出问题所在!

    如果说是对应的速度分析-使用speed-measure-webpack-plugin

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

    对应的可以看到每个Loader和插件执行耗时!

    体积分析-使用webpack-bundle-analyzer

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

    构建完成之后会在8888端口展示大小!

    对应的速度优化策略

    • 使用Webpack4
    • 多进程/多实例构建
    • 分包
    • 缓存
    • 缩小构建目标
    1. 使用新的Webpack带来的受益确实不错,那么使用Webpack4究竟好在哪里,为什么要使用Webpack4?
    • V8带来的优化(for or替代forEach,Map和Set替代Object,includes替代indexOf)
    • 默认使用更快的md4 hash算法
    • webpacks AST直接从loader传递给AST,减少了解析时间
    • 使用字符串方法替代正则表达式
    1. 多进程/多实例构建-资源并行解析可选方案

    thread-loader 可选方案

    • paraller-webpack
    • HappyPack

    多进程/多实例-使用HappyPack解析资源

    • 原理:每次Webpack解析一个模块,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']
        })        
    }
    

    对应的工作流程:

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

    中间部分:

    HappyThread[1,N]
    

    右边部分:

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

    多进程/多实例-并行压缩

    • 方法一:使用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参数
    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
            })
        ]   
    }
    

    分包-设置Externals

    • 思路:将React,react-dom基础包通过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'
                }
            ]
        })
    ]
    

    进一步分包-预编译资源模块

    • 思路:将React,react-dom,redux,react-redux基础包和业务基础打包成为一个文件
    • 方法:使用DLLPlugin进行分包,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'
            })
        ]
    }
    

    缓存

    • 目的:提升二次构建速度
    • 方法:使用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
            }
        })
    }
    

    缩小构建的目标:

    • 目的:尽可能的少模块构建
    • 比如:babel-loader不解析node_modules
    module.exports = {
        rules:{
            text:/\.js$/,
            loader:'happypack/loader',
            exclude:'node_modules'
        }
    }
    

    体积优化策略:

    • Scope Hoisting
    • Tree-shaking
    • 公共资源分离
    • 图片压缩
    • 动态Polyfill
    1. Scope-Hoisting原理

    原理:将所有模块的代码按照引用顺序放在一个函数作用域中,然后适当的重命名一些变量防止变量名冲突

    对比:通过scope hoisting可以减少函数声明代表

    示例代码:

    module.exports = {
        plugins:[
            new webpack.optimize.ModuleConcatenationPlugin()
        ]
    }
    
    • 对应的要求必须是ES6的语法,CJS的方式不支持

    Tree-shaking

    • 概念:1个模块可能有多个方法,只要其中的某个方法使用到了,则整个文件都会被打倒bundle里面去,Tree-shaking就是指把用到的方法打入bundle,没用到的方法会在uglify阶段被擦除掉!
    • 使用:webpack默认支持,在.babelrc里面设置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
                }
            }
        }]
    }
    

    构建体积优化-动态Polyfill

    babel-polufill

    方案 优点 缺点 是否采用
    babel-polyfill 官方推荐 单独构建 react前加载 包体积比较大 ×
    babel-plugin-transform-runtime 只能用到类或者方法 对体积较小 不适用于复杂的开发环境 ×
    map,set的polyfill 定制化体积小 重复造轮子 体积小染有用的都加载 ×
    polyfill-service 只给用户需要的 社区和维护 部分国内浏览器可能无法识别 但是可以降级返回所有的polyfill

    如何动态使用Polyfill service?

    • 使用polufill.io官方提供的服务
    • 基于官方自建的polyfill的服务

    总结和展望:

    • 遇到打包速度和体积问题如何优化
    • 体积优化的策略
    • 速度优化的策略
    • 弄清楚基本原理比较重要

    相关文章

      网友评论

          本文标题:Webpack极限打包优化

          本文链接:https://www.haomeiwen.com/subject/jcdkwctx.html