Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

【UI】关于webpack性能优化,我们能做些什么? #47

Open
fayeah opened this issue Jun 20, 2021 · 0 comments
Open

【UI】关于webpack性能优化,我们能做些什么? #47

fayeah opened this issue Jun 20, 2021 · 0 comments

Comments

@fayeah
Copy link
Owner

fayeah commented Jun 20, 2021

从结果出发

项目最近使用低代码前端框架amis进行开发,有很好的研发效率提升,但是构建速度却很慢,亟需进行优化。优化之后达到了将webpack构建速度提升80%左右的一个成绩,以下是优化前后的对比👇

30965ms ➡️ 6545ms

团队做了3件事情来达到这样的一个效果:

  1. split-chunks进行公共模块优化👇
    optimization: {
        splitChunks: {
            chunks: "all",
            cacheGroups: {
                vendorsa: {
                    chunks: 'all',
                    test: /(mobx-state-tree|react-color|react-dom-router|sortablejs|mobx-react)/,
                    priority: 100,
                    name: 'vendors-react-mobx',
                },
                venodrb: {
                    test: /lodash/,
                    priority: 100,
                    name: 'vendor-lodash',
                    chunks: 'all'
                },
            }
        }
    },
    
  2. external避免将比较大的第三方依赖打包到bundle中👇
    webpack.config.js:
    
    externals: [
        {
            'react': 'React',
            'react-dom': 'ReactDOM',
            'moment': 'moment',
            'mobx': 'mobx',
            'monaco-editor': 'monaco',
            'echarts': 'echarts',
            'jquery': 'jQuery',
            'hls.js': 'hls',
            'flv.js': 'flv',
        },
        function (context, request, callback) {
            if (/^moment\/.+$/.test(request)) {
                return callback(null, 'root ' + 'moment');
            }
            if (/^tinymce\/.+$/.test(request)){
                return callback(null, 'root ' + 'tinymce');
            }
            if (/^froala-editor\/.+$/.test(request)){
                return callback(null, 'root ' + 'froala');
            }
            if (/^echarts\/.+$/.test(request)){
                return callback(null, 'root ' + 'echarts');
            }
            // 继续下一步且不外部化引用
            callback();
        },
    ]
    
    index.html:
    <script src="https://unpkg.com/react@16.8.6/umd/react.production.min.js"></script>
    <script src="https://unpkg.com/react-dom@16.8.6/umd/react-dom.production.min.js"></script>
    <script src="https://unpkg.com/moment@2.29.1/min/moment.min.js"></script>
    <script src="https://unpkg.com/moment@2.29.1/min/locales.min.js"></script>
    <script src="https://unpkg.com/mobx@4.5.2/lib/mobx.umd.min.js"></script>
    
  3. ts-loader的优化:
    {
        test: /\.tsx?$/,
        use: [
          {
            loader: 'ts-loader',
            options: {
              transpileOnly: true
            }
          }
        ],
        exclude: /node_modules/
    },
      ...
    
    plugins: [
        new ForkTsCheckerWebpackPlugin(),
    ]
    

基于这次优化做了功课,看了一些资料,看看还有哪些可以优化的地方。

webpack是什么

官网的定义:

webpack is a static module bundler for modern JavaScript applications. When webpack processes your application, it internally builds a dependency graph which maps every module your project needs and generates one or more bundles.

也就是说 webpack 是一个用于现代 JavaScript 应用程序的静态模块打包工具,从入口出发,找到入口文件所有的依赖,生成浏览器可以用的bundle文件。webpack的出现使得前端的工程化更加地丰富。从webpack在2013的第一次release(v1.0.0-beta2)开始,至今已经有8、9年的历史了,是一个相当成熟的工具,其生态也比较完善,所以前端圈用webpack也是非常地广泛。

版本

尽量用较新的版本,新版本想较之前都会有一定的性能提升和优化,包括Node和Webpack。要注意的是Node.js v8.9.10 - v9.11.1ES6的SetMap会有性能回退问题,现在LTS的node已经是v14.16.0,所以假设Node版本已经较新,并且用的是WP4(webpack4)。目前还不建议对求稳的或者已经很庞大的项目立即升级到WP5,其一是因为webpack生态里面并不一定所有的插件都能跟的上最新的版本,可能会出现兼容性的问题;其二由于webpack5还并未被广泛地应用,到新版本的稳定和成熟还是需要一定的时间,为避免不必要的bug,建议暂时使用webpack4

为什么要优化

对于开发者来说,每次在build的时候不希望花费较长的时间,优化构建速度能够减少开发成本;对于用户而言,优化bundle文件的数量和大小能减少用户的流失率,提升用户体验。所以webpack的性能优化是一个非常关键的技术手段。

优化手段

两个测量工具

  • speed-measure-webpack-plugin(SMP),要对webpack的构建速度进行优化,得知道要优化的重点在哪里,而该插件则是帮助检查哪些地方需要进一步优化的工具。
  • webpack-bundle-analyzer,虽然webpack也有官方的分析工具,社区也有许多其他的工具可以参考,但是通过资料、技术分享以及项目经验,webpack-bundle-analyzer用的还是不错的,它可以将bundle展示为交互式、可缩放的树状图形式,使用起来非常便捷。

三个可优化阶段

webpack构建大概可分为loader解析 -> 依赖搜索 -> 打包等三个阶段,就这三个阶段我们分别展开阐述如何去优化。

loader解析:

  • include/exclude,对于loader而言,不需要对项目中所有的文件进行文件转换,应将loader应用于最少数量的必要模块,常见的配置:

    {
        ...,
        exclude: /node_modules/
    },
    
  • 如果项目中用到了ts-loader,那就要小心了,因为如果不做额外的配置,会发现构建速度是非常耗时的。原因是因为ts-loader在每次构建的时候都会对所有文件进行类型检查,当项目越来越庞大,会发现构建速度越来越慢。这个时候需要设置transpileOnly: true来提高构建速度,该配置只处理编译而不做类型检查;然而类型检查是使用TypeScript的初衷,可以使用fork-ts-checker-webpack-plugin插件来在单独的进程中做类型检查。那性能和类型检查都能cover到。

  • 如果使用的是babel-loader,可以设置其cache相关的选项,比如cacheDirectorycacheCompression等;

  • cache-loader: 利用文件的modifier time来检查文件是否更新,如果没有更新则利用缓存的内容,是一个轻量级的比较。特点是在第一次构建的时候比较慢,后面的构建会快很多。但是用它也要特别注意,最好用在性能消耗比较昂贵的地方,否则基本没有什么效果。该loader已经被作者Archive了,因为webpack5内置了cache的相关配置,将来如果升级就不需要它了。使用cache-loader时要放在其他loader的前面。

  • thread-loader:也是应用于比较昂贵的地方,可以将打包任务划分多个node进程,把模块一次分配给这些进程,实现多进程构建。跟cache-loader一样也需要放到其他loader的前面。

    {
        ...,
        use: [
            'thread-loader',
            // 昂贵的loader (e.g babel-loader)
        ],
    },
    
  • 最后一点是,尽量少用工具,因为每个loader/plugin都有其启动时间,不用就不会有性能问题啦。

依赖搜索:

  • 减少 resolve.modules, resolve.extensions等 中条目数量,因为IO操作比较消耗性能;

  • 基本上webpack对这些配置有默认值,比如resolve.modulesnode_modules,告诉webpack解析模块时应该搜索node_modules目录;resolve.extensions默认值为['.wasm', '.mjs', '.js', '.json'],如果用了TypeScript还是要配置一下的,extensions是说在引入模块时可以不需要带扩展:

    import File from '../path/to/file';
    

打包: Smaller = Faster

  • splitChunks,本着“小即是快”的原则,尽量使chunk包越小越好。在webpack4之前,可以使用CommonsChunkPlugin来避免模块与模块之间的重复依赖,webpack4内置了optimization.splitChunks,可开箱即用。splitChunks有它默认的行为,不同的项目根据需求做不同的调整。可以设置不同的cacheGroup,拆分前必须共享模块的最小chunk数量等等,能最大程度地优化重复依赖的问题。一个简单的🌰 :

    splitChunks: {
      chunks: "all",
      cacheGroups: {
        lodash: {
          test: /lodash/,
          priority: 1,
        },
      }
    }
    
  • externals,防止将某些 import 的包打包到 bundle 中,而是在运行时再去从外部获取这些扩展依赖。例如:

    externals: [
        {
            'moment': 'moment',
        }
        ...
    ]
    

当然需要在index.html里面引入cdn依赖,否则在runtime无法找到相应的模块:<script src="https://unpkg.com/moment@2.29.1/min/moment.min.js"></script>

多环境

生产环境 生产环境关注与压缩bundle、更轻量的source map等,建议不同环境写不同的配置,当然可以有共用的配置,利用webpack-merge可以实现配置共用;对于devTools,推荐使用source-Map,相对于inline-source-mapeval-cheap-module-source-map性能好一点;代码压缩,在WP5中内置了terser-webpack-plugin,现在使用WP4的话,需要安装插件,这个插件功能非常强大,除了基本的压缩功能以外,还可以使用多进程并发构建,以及去除注释等功能;不带路径的配置,path-info会在bundle中包含模块信息的注释,但在庞大的项目中,会导致GC性能很差,应该关闭;

开发环境 同样地,生产环境有些配置也不适用于开发环境,比如TerserPlugin就不需要,因为在开发环境中压缩代码是没有意义的;devTools的最佳实践是eval-cheap-module-source-map,我现在的项目比较轻量,但是也能看出对比:

`inline-source-map`:5205ms

VS

`eval-cheap-module-source-map`: 4744ms

虽然是不到1000ms的差距,苍蝇肉也是肉不是?而且将来代码量越来越庞大的时候,差距就更明显了。

当然还有其他的可以优化的方法,比如使用ES module,能更好地利用webpack的tree shaking功能;Dll,为更改不频繁的代码生成单独的编译结果,但却是一个配置比较复杂的过程;还有对图片的压缩等等。以上是对于webpack4性能优化基本的配置,期待webpack5成熟稳定的那一天。

参考

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant