Webpack极限打包优化

2019-11-23  本文已影响0人  井润

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

其中讲到的点如下所示:

01|为什么需要构建工具?

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

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

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有什么区别吗?

目标代码:

02|Grunt,Gulp和WebPacl的对比

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中对于资源的处理又是如何做的呢? 将对应的资源都是当成模块来处理!

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

03|初级分析-使用Webpack内置的stats
"scripts":{
    "build:stats":"webpack --env production --json > stats.json"
}    
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端口展示大小!

对应的速度优化策略

  1. 使用新的Webpack带来的受益确实不错,那么使用Webpack4究竟好在哪里,为什么要使用Webpack4?
  1. 多进程/多实例构建-资源并行解析可选方案

thread-loader 可选方案

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

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

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

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
                }
            }   
        })
    ]
}
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

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'
            }
        ]
    })
]

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

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'
        })
    ]
}

缓存

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
        }
    })
}

缩小构建的目标:

module.exports = {
    rules:{
        text:/\.js$/,
        loader:'happypack/loader',
        exclude:'node_modules'
    }
}

体积优化策略:

  1. Scope-Hoisting原理

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

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

示例代码:

module.exports = {
    plugins:[
        new webpack.optimize.ModuleConcatenationPlugin()
    ]
}

Tree-shaking

公共资源分离

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
                }
            }
        }
    }
}

图片压缩:

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?

总结和展望:

上一篇下一篇

猜你喜欢

热点阅读