当vue遇到老的项目启动和打包速度慢怎么办? webpack-低版版本-编译启动速度和打包速度优化方案

w e b p a c k 优化背景 webpack优化背景 webpack优化背景

  • 前段时间我很幸运地接了一个古老的项目,webpack的版本还停留在4.0的版本,本来没想优化的,但是由于每次启动需要6分钟,保存一下页面热启动也需要2分钟,直接把我整崩溃了,这种心情 一言难尽
  • 我开发了一周,每天大概浪费2个小时在等待页面启动和编译上,我真的崩溃了
  • 于是我决定使用一天的时间优化webpack配置
  • 开始了我的优化之旅

优化成果

  • 第一次启动 10s
  • 热启动 5s 之内
  • 第一次打包 20s之内
  • 再次打包 10s之内

优化前提

  • 仔细阅读webpack的开发文档
  • 注意 很多api是只有webpack5的版本才支持的

优化的最大痛点

  • webpack 的版本过低(4.0),很多优秀的api和属性甚至是打包编译思想都没有用到

突如其来的一道灵光,换壳,将vue-cli3.6的版本直接升级到vue-cli5.0

  • 业务代码不变,改变项目的整体壳子和vue-clid等配置

可行性分析

业务代码影响分析

  • 都是vue项目并且vue的版本都是用的是vue2.6 的版本,所以业务代码是没有什么风险点的,唯一要改的是配置

配置代码分析

  • 由于vue-cli3.6升级到vue-cli5.0之后,webpack 的优秀属性就都可以使用了

升级vue-cli步骤

使用vue-cli5脚手架搭建一个空壳项目

  • 使用vue-cli5 搭建一个vue的基础项目

替换全局代码

替换业务代码

  1. 将src文件夹全部替换
  2. 将public 文件夹替换

package.json 修改

  1. 将vue-cli3.6老项目中的全局配置项和vue-cli5新壳子中的配置项目对比,把vue-cli5中出现过的依赖项直接删除,使用vue-cli5默认的
  2. 将vue-cli3.6老项目中出现的项目依赖项移捞出来放到新壳子中去
  3. 删除代码中没有引用的依赖项
  • 由于老项目中的依赖项可能是其他项目搬过来的,所以必定会出现没有用到的依赖项
  • 在代码中src文件夹下全局搜索,判断插件是否有使用到,没有用到的直接删除

.babelrc 文件修改

  • 由于vue-cli3.6和vue-cli5.0的babel处理方式不太一样,所以需要修改
  • 老的代码就不放了
vue-cli5的.babelrc代码
{
  "presets": [
    "@vue/cli-plugin-babel/preset"
  ],
  "plugins": [
    "equire",
    [
      "import",
      {
        "libraryName": "view-design",
        "libraryDirectory": "src/components"
      }
    ]
  ]
}

修改vue.config.js

  • 壳子已经替换完毕,现在开始webpack 的配置

配置 vue.config.js

定义环境变量

const isDev = process.env.NODE_ENV == 'development'

transpileDependencies 关闭

  • 关闭之后,能够提编译速度
 transpileDependencies: isDev ? false : true,//转译依赖

  • 默认情况下 babel-loader 会忽略所有 node_modules 中的文件。你可以启用本选项,以避免构建后的代码中出现未转译的第三方依赖。

开启 terser-webpack-plugin 代码压缩

开启参数

  • minimize 开启压缩

TerserPlugin 配置

  • parallel 最大并行进程
  • cache 是否开启缓存
  • sourceMap 是否开启 sourceMap
  • terserOptions
    • compress 压缩选项
    • compress.drop_console 是否删除console 生产环境删除,开发环境保留
    • compress.drop_debugger 是否删除debugger 生产环境删除,开发环境保留
    • output.comments 是否删除comments 生产环境删除,,开发环境保留
let minimizeConfig = {                                            
    minimize: true,
    minimizer: [new TerserPlugin({
    parallel: 4,
    cache: true,
    sourceMap: false,
    terserOptions: {
        compress: {
        drop_console: isDev ? false : true,
        drop_debugger: isDev ? false : true,
        },
        output: {
        comments: false,
        },
    },
    }
    )],
    concatenateModules: false, // 公共代码整合,生产环境下被启用
}

开启摇树优化

  • 将项目中重复的代码合并,删除多余的代码
config.optimization = {

    // runtimeChunk: true,
    usedExports: isDev ? false : true,//开启要数优化 tree shaking
    sideEffects: false,

    splitChunks: {
    chunks: 'all',
    minSize: 20000,
    minRemainingSize: 0,
    minChunks: 1,
    maxAsyncRequests: 30,
    maxInitialRequests: 30,
    enforceSizeThreshold: 50000,
    cacheGroups: {
        //公用模块抽离
        common: {
        chunks: 'initial',
        minSize: 0, //大于0个字节
        minChunks: 2, //抽离公共代码时,这个代码块最小被引用的次数
        },
        //第三方库抽离
        vendor: {
        priority: 1, //权重
        test: /node_modules/,
        chunks: 'initial',
        minSize: 0, //大于0个字节
        minChunks: 2, //在分割之前,这个代码块最小应该被引用的次数
        },
        default: {
        minChunks: 2,
        priority: -20,
        reuseExistingChunk: true
        }
    }
    }
}

watchOptions 忽略node_modules

config.watchOptions = {
    ignored: /node_modules/,//忽略node_modules包文件
    aggregateTimeout: 600,//多次修改批量更新
    poll: 1000//每秒检查一次变动
}

devtool配置

config.devtool = isDev ? 'source-map' : false//错误信息

开发环境

  • 启用sourcemap
  • devtool 设置值为 source-map

生产环境

  • 关闭sourcemap
  • 注意设置为false,否则无法彻底关闭sourcemap

开启cache缓存

  • 缓存是前端优化的常规手段,webpack 中同样可以
  • 缓存分内存和磁盘文件缓存,此处是使用文件磁盘缓存
    • type 使用 filesystem
    • allowCollectingMemory 收集在反序列化期间分配的未使用的内存,仅当 cache.type 设置为 ‘filesystem’ 时生效。这需要将数据复制到更小的缓冲区中,并有性能成本。
  • cacheDirectory 文件缓存的目录
    • 指定为档期目录下的 .temp_cache 文件夹下
    • 它下面分development 和 production 文件夹
config.cache = {
    type: 'filesystem',
    allowCollectingMemory: true,
    cacheDirectory: path.resolve(__dirname, '.temp_cache'),
}

  • 注意 建议定期删除 temp_cache 文件夹,以免占用过多磁盘空间
小伙伴担忧
是否会卡顿
  • 缓存太多是否会导致,内存爆掉甚至于卡顿
  • 我在此明确地告诉你,不会,原因很简单
    • 这个缓存是基于磁盘的,不是内存
    • 建议一周删除一次缓存文件

output 配置

      config.output = {
        clean: true, // 在生成文件之前清空 output 目录
        compareBeforeEmit: false,// 当在磁盘中已经存在有相同内容的文件时,webpack 将不会写入输出文件。
        filename: '[name].[contenthash].bundle.js',// wenpack打包后的文件名
        chunkFilename: 'js/[name].[contenthash].bundle.js',// 异步加载的模块
        path: path.join(__dirname, 'testProject'),
        publicPath: isDev ? '/' : '/testProject/',
      }

备注 chainWebpack 和 configureWebpack 区别

官方介绍

  • chainWebpack 是一个函数,会接收一个基于 webpack-chain 的 ChainableConfig 实例。允许对内部的 webpack 配置进行更细粒度的修改。
  • configureWebpack 如果这个值是一个对象,则会通过 webpack-merge 合并到最终的配置中

通俗区别

  • chainWebpack用于修改,会和默认配置项合并,追加某个属性值
  • configureWebpack用于合并,向原有配置项追加配置,直接添加整个配置项
  • 所以,只修改某个属性使用 chainWebpack,添加配置项使用configureWebpack

webpack辅助工具

  • 可帮助我我们更好的优化代码

webpack-bundle-analyzer

  • 代码分析工具,可分析打包之后的文件

speed-measure-webpack-plugin

  • 打包的速度分析,和时间分析插件
config.plugins.push(new BundleAnalyzerPlugin())
config.plugins.push(new WebpackBar({ name: 'PC', color: '#07c160' }))
const {defineConfig} = require('@vue/cli-service')
const TerserPlugin = require('terser-webpack-plugin');
const path = require('path')
const webpack = require('webpack');
const BundleAnalyzerPlugin = require("webpack-bundle-analyzer").BundleAnalyzerPlugin
const resolve = dir => path.join(__dirname, dir)
const packageName = require('./package.json').name
const isDev = process.env.NODE_ENV == 'development'
const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");
const WebpackBar = require('webpackbar');

module.exports = defineConfig({
  // productionSourceMap: false, // 关闭生产环境的 source map
  publicPath: isDev ? '/' : '/testProject/',
  outputDir: 'testProject',
  lintOnSave: false,
  transpileDependencies: isDev ? false : true,//转译依赖
  chainWebpack: config => {
    config.plugin('speed-measure-webpack-plugin').use(SpeedMeasurePlugin).end();
    if (!isDev) {
      config.plugins.delete('prefetch');
      // 移除 preload 插件
      config.plugins.delete('preload');
    }
    config.plugin('speed-measure-webpack-plugin').use(SpeedMeasurePlugin).end();
  },
  configureWebpack: config => {
    if (!isDev) {
      config.entry = "./src/main.js"                  
      config.output = {
        clean: true, // 在生成文件之前清空 output 目录
        compareBeforeEmit: false,// 当在磁盘中已经存在有相同内容的文件时,webpack 将不会写入输出文件。
        filename: '[name].[contenthash].bundle.js',// wenpack打包后的文件名
        chunkFilename: 'js/[name].[contenthash].bundle.js',// 异步加载的模块
        path: path.join(__dirname, 'testProject'),
        publicPath: isDev ? '/' : '/testProject/',
        // qiankun接入配置
        library: `${packageName}-[name]`,
        libraryTarget: 'umd', // 把微应用打包成 umd 库格式
        chunkLoadingGlobal: `webpackJsonp_${packageName}`,//webpack5 output.jsonpFunction 更名为 output.chunkLoadingGlobal
      }
      config.plugins.push(new BundleAnalyzerPlugin())
      config.plugins.push(new WebpackBar({ name: 'PC', color: '#07c160' }))
    }
    config.resolve.alias =
      // 设置路径别名,设置后需保持jsconfig.json内一致
      {
        '@': resolve('src'),
        '_c': resolve('src/components')
      }
    let minimizeConfig = {                                            
      minimize: true,
      minimizer: [new TerserPlugin({
        parallel: 4,
        cache: true,
        sourceMap: false,
        terserOptions: {
          compress: {
            drop_console: isDev ? false : true,
            drop_debugger: isDev ? false : true,
          },
          output: {
            comments: false,
          },
        },
      }
      )],
      concatenateModules: false, // 公共代码整合,生产环境下被启用
    }
    config.optimization = {

      // runtimeChunk: true,
      usedExports: isDev ? false : true,//开启要数优化 tree shaking
      sideEffects: false,

      splitChunks: {
        chunks: 'all',
        minSize: 20000,
        minRemainingSize: 0,
        minChunks: 1,
        maxAsyncRequests: 30,
        maxInitialRequests: 30,
        enforceSizeThreshold: 50000,
        cacheGroups: {
          //公用模块抽离
          common: {
            chunks: 'initial',
            minSize: 0, //大于0个字节
            minChunks: 2, //抽离公共代码时,这个代码块最小被引用的次数
          },
          //第三方库抽离
          vendor: {
            priority: 1, //权重
            test: /node_modules/,
            chunks: 'initial',
            minSize: 0, //大于0个字节
            minChunks: 2, //在分割之前,这个代码块最小应该被引用的次数
          },
          default: {
            minChunks: 2,
            priority: -20,
            reuseExistingChunk: true
          }
        }
      }
    }

    if (!isDev) {
      config.optimization = Object.assign(config.optimization, minimizeConfig)
      console.log(config.module)
    }

    config.watchOptions = {
      ignored: /node_modules/,//忽略node_modules包文件
      aggregateTimeout: 600,//多次修改批量更新
      poll: 1000//每秒检查一次变动
    }
    config.devtool = isDev ? 'source-map' : false//错误信息
    config.cache = {
      type: 'filesystem',
      allowCollectingMemory: true,
      cacheDirectory: path.resolve(__dirname, '.temp_cache'),
    }
  }
})

优化成果

初次 npm run dev 运行速度

  • 所有的缓存文件都清除,运行时间大概在36秒
    在这里插入图片描述

基于缓存 npm run dev 运行速度

  • 只需要 4.7秒
    在这里插入图片描述

初次 npm run build 打包速度

在这里插入图片描述

基于缓存 npm run build 打包速度

  • 9.64秒

在这里插入图片描述

项目体量

src文件数量和大小

  • 770个文件,10MB
    在这里插入图片描述

项目依赖大小

  • 781MB
    在这里插入图片描述

打包后文件 大小

  • 仅有 9.8MB
    在这里插入图片描述

个人总结

  • webpack 本质上也是js,我们不会配置,可能只是不太熟悉,不要有恐惧心理
  • 先看仔细阅读文档,重点看他的优化方案,整合下即可结合到项目中

致谢

  • 感谢webpack官方文档提供的文档说明
  • 感谢我的项目组给了我挑战自己的机会
  • 感谢我的导师给予我的帮助

  • 感谢您百忙之中抽时间阅读我写的博客,谢谢您的肯定,也希望对您能有所帮助
  • 如果您有更好的见解请在评论区留言或者私聊我,期待与您的交流
  • 20
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值