webpack —— 优化开发体验

webpack 优化从两个方面入手。
一、优化开发体检。包括优化构建速度和优化使用体验
二、优化输出质量,包括减少用户能感知到的加载时间(首屏加载时间)和提升流畅度(提升代码性能)。参考链接优化输出质量

项目背景:我目前的项目,功能巨多,业务庞大。打包上面的确有很多不合理的地方以致于打包时间最长有过20分钟的情况。平均正常也得800-900秒。在另一个同事的积极推动建议下,开启了打包优化之旅。我的主要目标是优化构建速度。所以这块验证的比较多。其他的知识点算是总结了。我主要参考的是深入浅出webpack这本书。

优化构建速度

是按我自己项目中的构建优化时间从大到小排列的。

  • 通过 Node.js API 启动 Webpack,构建时间减少了一半。减少到400-500秒
    无意中发现的方式。因为本身项目比较老,没有用脚手架工具。参考用脚手架create-react-app工具搭建的项目的配置。发现他们启动的方式是通过Node.jsAPI启动的。当时做的时候,不知道命名,是看了参考书,才知道原来有这个定义。参考 通过 Node.js API 启动 Webpack
    书上这样介绍:通过 API 去调用并执行 Webpack 比直接通过可执行文件启动更加灵活,可用在一些特殊场景(没有介绍具体的使用场景,但是发现脚手架都是这样的用的)
    总结:webpack执行的两种方式:

    1. Webpack 其实是一个 Node.js 应用程序,它全部通过 JavaScript 开发完成。 在命令行中执行 webpack 命令其实等价于执行 node ./node_modules/webpack/bin/webpack.js。
     // package.json
     "scripts": { 
          "start": "webpack", 
      },
    
    1. 通过 Node.js API 启动 Webpack。
      const config = require('webpack.config.js')
      const webpack = require('webpack')
      const compiler = webpack(config)
      compiler.run((err, stats) => {
        // console.log(err, stats)
      })
      

    这块没有理解为啥通过Node.jsAPI的方式会快这么多。猜测应该是直接webpack的时候,做了一些判断啥,然后才走的webpack-cli。然而Node.jsAPI是直接走到webpack-cli。代码少了一部分。因而时间少。不过没有证实,也不确定少走什么代码,时间这么大的差异。(如果有知道的,欢迎补充,我会再更新。如果我后续看懂了源码,也会再补充。

  • 使用 ParallelUglifyPlugin 多进程压缩,构建时间减少了200s左右,平均值在250秒
    代码构建的时候,需要先经过loader处理,然后再压缩。由于压缩 JavaScript 代码需要先把代码解析成用 Object 抽象表示的 AST 语法树,再去应用各种规则分析和处理 AST,导致这个过程计算量巨大,耗时非常多。这里借鉴使用 HappyPack中介绍过的多进程并行处理的思想引入到代码压缩中。(HappyPack使用 ,在我的项目中,没有减少时间,反而增加了几秒。所以排列在最后面。肯定有他的好处,但是要慎用,因为开启进程也会耗费时间)使用此项配置,时间减少到300秒左右。

const ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin');
module.exports ={
  entry: 'main.js', 
  output: {
    filename: '[name].[chunkhash].js',
    chunkFilename: '[name].[chunkhash].chunk.js'
  },

  plugins: [ 
    // 使用 ParallelUglifyPlugin 并行压缩输出的 JS 代码
    new ParallelUglifyPlugin({
      // 传递给 UglifyJS 的参数
      uglifyJS: {
        output: {
          // 最紧凑的输出
          beautify: false,
          // 删除所有的注释
          comments: false,
        },
        compress: {
          // 在UglifyJs删除没有用到的代码时不输出警告
          warnings: false,
          // 删除所有的 `console` 语句,可以兼容ie浏览器
          drop_console: true,
          // 内嵌定义了但是只用到一次的变量
          collapse_vars: true,
          // 提取出出现多次但是没有定义成变量去引用的静态值
          reduce_vars: true,
        }
      },
    }), 
  ]
 }
  • 修改压缩图片的loader配置。减少130秒左右,到120秒
    原来loader配置中用了压缩图片的功能,image-webpack-loader,导致时间变长。去掉后时间明显减少。压缩倒是很大。由120kb的图片可以压缩到40kb。但是时间很久。一开始去掉了此配置,换成了 imagemin-webpack-plugin插件做压缩。压缩质量设置成一样的,都是65-90。时间却省了不少。基本跟现在一样。后来修改了loader的原有配置,只剩下quality一项。时间也会减少很多。结论是:两个用哪个都行。image-webpack-loader的配置挺重要的。代码配置如下
// 原来比较慢的配置
  {
      test: /\.(jpg|png|jpeg|gif|pdf|mp3|aac)$/,
      loaders: [
         'file-loader',
        {
           loader: 'image-webpack-loader',
           query: {
             progressive: true,
             optimizationLevel: 7,
             interlaced: false, 
             pngquant: { 
              quality: [0.65, 0.9],
              speed: 4
             }
           }
         }
       ]
  },

// 去掉某些配置后,压缩时间减少了
  {
      test: /\.(jpg|png|jpeg|gif|pdf|mp3|aac)$/,
      loaders: [
         'file-loader',
        {
           loader: 'image-webpack-loader',
           query: { 
             pngquant: { 
              quality: [0.65, 0.9], 
             }
           }
         }
       ]
  }, 
// 用插件的配置 这个也挺快的
   new ImageminPlugin({
        pngquant: {
            quality: '65-90'
        }
    }),
  • 缩小文件搜索范围(只采用了第一项,整体打包时间到50s左右)

    • 优化 loader 配置。减少70秒左右。
      module.exports = {
      module: {
       rules: [
         {
             // 如果项目源码中只有 js 文件就不要写成 /\.jsx?$/,提升正则表达式性能
           test: /\.js$/,
           // babel-loader 支持缓存转换出的结果,通过 cacheDirectory 选项开启
           use: ['babel-loader?cacheDirectory'],
            // 只对项目根目录下的 src 目录中的文件采用 babel-loader
           include: path.resolve(__dirname, 'src'),
         },
       ]
      },
      };
      
    • 优化 resolve.modules 配置(未使用)
      此项也能减少50秒左右。但是此项引发了我项目的bug。参考一个webpack构建速度优化误区。我的项目有用到websocket。按照这篇文章的意思,我觉得可能是最外层的node-modules目录下和插件目录下都存在一个组件。然后修改了此项,改变了组件的引用地址。本来应该用组件下的node-modules里面的某项,结果用了最外层的。由于版本不一致,导致的错误。所以此项没有真实用到项目里面。
       module.exports = {
         resolve: {
         // 使用绝对路径指明第三方模块存放的位置,以减少搜索步骤
         // 其中 __dirname 表示当前工作目录,也就是项目根目录
           modules: [path.resolve(__dirname, 'node_modules')]
         },
       };
    
    • 优化 resolve.mainFields 配置(未使用)
      减少20-30秒,但是我的项目是允许在浏览器端的。也就是target:web。如果改成以main为先,项目也存在错误。

    resolve.mainFields 的默认值和当前的 target 配置有关系,对应关系如下:
    当 target 为 web 或者 webworker 时,值是 ["browser", "module", "main"]
    当 target 为其它情况时,值是 ["module", "main"]

         // 我原本的是这样
         mainFields: [
             'browser',
             'jsnext:main',
             'main'
        ]
        // 为了缩小查询的步骤 建议改成。但是这种改法,在我们做网站的这种项目中,都是运行在浏览器端的。所以不建议。会出错
         mainFields: [ 
              'main',
              'browser',
             'jsnext:main',
        ]
    
    • 优化 resolve.alias 配置(未使用)
      减少30-40秒。原理是直接引用压缩好的文件,省去了打包。我把react-dom也放到这里,优化时间会多一点,其他的看不太出来。但是react-dom 用了后就报错了。于是也没有真正使用。
    • 优化 resolve.extensions 配置(未使用)
      在导入语句没带文件后缀时,Webpack 会自动带上后缀后去尝试询问文件是否存在。 resolve.extensions 用于配置在尝试过程中用到的后缀列表,默认是: extensions: ['.js', '.json'] 。这项项目中本来就有。所以没有用。但是通过这项,我觉得应该在平时书写代码的时候加上后缀,也会加快读取。
    • 优化 module.noParse 配置(未使用)
      通过配置 module.noParse 忽略对 没采用模块化的文件的递归解析处理,这样做的好处是能提高构建性能。跟alias类似。忽略掉react-dom,报一样的错误。也没有再用了。
  • 使用 DllPlugin,这项没验证。
    DllPlugin 主要是webpack内置的插件。就是先把某些 不用变动的包 单独打包出去,然后再静态引入,这是我们再配置webpack告诉webpack 哪些包不用再编译了,这样就能很大程度上提高我们的编译速度。 node-module里面的很多我们需要用的包,以vue项目为例,比如vue echart element等,就用这个打包我们需要用的。里面还有很多是webpack的依赖,那些不需要我们打包。

    • 1.建立一个webpack.dll.js的文件 里面放所有提前编译好的文件

      let path = require('path')
      let {CleanWebpackPlugin} =require('clean-webpack-plugin')
      let webpack = require('webpack')
      module.exports = {
          mode: 'production',
          entry: {
              vue:['vue']
          },
          output: {
              filename: 'dll_[name].js',
              path: path.resolve(__dirname, 'dist'),
              library:'dll_[name]'
          },
          plugins: [
              new CleanWebpackPlugin(),
              new webpack.DllPlugin({
                  name: 'dll_[name]',  // name = library
                  path:path.resolve(__dirname,'dist','manifest.json')
              })
          ]
      }
      
    • 2.package.json 中配置编译这些文件的命令

       "scripts": {
            "dev": "webpack-dev-server --config webpack.config.dev.js",
            "build": "webpack --config webpack.config.dev.js",
            "dllVue": "webpack --config webpack.dll.js"
        },
      

      此时 通过启动 npm run dllVue 即可把这些不用每次都编译的文件编译好

    • 3.webpack.config.js 中配置 打包的时候 不去编辑以上打包好的文件

       plugins: [
           new webpack.DllReferencePlugin({
               // 打包编译的时候,先去这个清单中查找 清单中没有再去进行打包
               manifest:path.resolve(__dirname,'dist','manifest.json')
           }),
           new CleanWebpackPlugin({
               // 告诉这个插件 不用清除这些文件 
               cleanOnceBeforeBuildPatterns:['**/*','!dll_*','!mani*']
           }), 
       ],
      
    • 4.本地开发的时候要 查找 已经编译好的文件 这些文件是静态文件 所以要在devServer中设置contentBase

       devServer: {
           port: 8999,
           // hot:true, 
           // 查找到dist里面的文件
           contentBase:'dist',
            <!--DevServer起服务后 文件都是在他的内存中的  然后如果要访问我们本地的文件的话 他是访问不到的  所以要用这个 contentBase指向我们本地的文件夹。也就是把contentBase对应的文件,作成他的服务中的静态文件,这样通过服务就可以访问到文件。-->
       },
    
    • 5.index.html页面要引入这个文件
       <script src="./dll_vue.js"></script> 
    
  • 使用 HappyPack(未使用,使用了时间没减少)
    多进程处理文件,加快速度的。但是开启进程也需要时间。慎重使用。
    happyPack已不再维护,官网推荐的是thread-loader.通过在loader处理之前,拦截开启进程。使用过程时间未减少,反而增了几秒,没有 ParallelUglifyPlugin效果明显。(可能这个说法不准确,只是以我自己的项目为例显示出来的效果)

优化使用体验

  • 使用自动刷新
  • 开启模块热替换
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,258评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,335评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,225评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,126评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,140评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,098评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,018评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,857评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,298评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,518评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,678评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,400评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,993评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,638评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,801评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,661评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,558评论 2 352

推荐阅读更多精彩内容