如何优化你的 vue-cli 项目?

在日常开发中,最容易让人注意的就是项目编译打包的时间,特别是在较频繁打包部署时,这个时间显得很漫长。

例如:之前接手的一个项目 "冷启动" 时编译过程花费了约 86 秒:

图片

这能忍吗?显然不能,于是打算将其进行优化,否则太影响开发体验了,下面就是在优化过程中做的一些处理。

图片

分析耗时模块

查看 vue-cli 内置配置

使用 vue-cli-service inspect 可以很方便的查看 vue-cli 内置的配置内容,在终端输入:vue inspect --mode production webpack.config.production.js,就会在项目和 src 同级目录中生成 webpack.config.production.js 文件.

图片

分析工具

要得到耗时的模块,那就需要用到一些分析工具了,例如:

  • speed-measure-webpack-plugin

  • webpack-bundle-analyzer

speed-measure-webpack-plugin

这个插件可以测量网页包构建速度,并会输出各个模块编译的时长,可以帮助我们更好的找到耗时模块。

在 vue.config.js 配置

 
const SpeedMeasurePlugin = require('speed-measure-webpack-plugin');module.exports = {  ...,  configureWebpack: (config) => {      ...      config.plugins.push(        new SpeedMeasurePlugin(),      );    },};
 

重新启动

从下图面的输出结果中,很容易看到对应的耗时模块.

图片

图片

webpack-bundle-analyzer

这个插件以可视化网页包输出文件的大小,并且提供了交互式可缩放的树形图.

在 vue.config.js 配置

  const SpeedMeasurePlugin = require('speed-measure-webpack-plugin');+ const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;module.exports = {  ...,  configureWebpack: (config) => {      ...      config.plugins.push(        new SpeedMeasurePlugin(),      + new BundleAnalyzerPlugin()      );    },};
 

重新启动

从下图面的输出结果中,可以很容易的观察出每个模块打包后的大小.

图片

图片

优化

thread-loader —— 开启多线程优化

根据 speed-measure-webpack-plugin 输出的编译时间显示,以下几个 loader 编译时间比重较大:

  • vue-loader

  • ts-loader

  • babel-loader

  • image-webpack-loader

  • postcss-loader 关于这一部分就可以通过 thread-loader 进行优化,因为它能将这些非常耗时的内容单独放到另一个线程中执行,但并不是针对所有的 loader 都做这个处理,因为这个处理本身也是有较大的开支。

注意:仅在耗时的操作中使用 thread-loader,否则使用 thread-loader 会后可能会导致项目构建时间变得更长,因为每个 worker 都是一个独立的 node.js 进程,其开销大约为 600ms 左右,同时还会限制跨进程的数据交换等。

在回顾下前面,没有使用 thread-loader 前项目的 "冷启动" 时间约 86 秒:

图片

只针对 babel-loader 使用 thread-loader 后项目的 "冷启动" 时间约 78 秒:

PS:在对其他 loader 使用 thread-loader 发现时间更长,因此取尝试后的最优结果

图片

hard-source-webpack-plugin —— 使用缓存优化

webpack 中几种缓存方式:

  • cache-loader

  • hard-source-webpack-plugin

  • babel-loader 的 cacheDirectory 标志

以上这些缓存方式都有首次启动时的开销,即它们会让 "冷启动" 时间会更长,但是二次启动能够节省很多时间.

vue-cli 已经内置了 cache-loaderbabel-loader 的 cacheDirectory 标志,其中对应的配置如下:

图片

默认情况下的 "二次启动" 时间约为 38 秒,原因是 "冷启动" 时已经将 babel-laoder、ts-loader、vue-loader 进行了缓存:

图片

图片

先配置 hard-source-webpack-plugin 插件:

​​​​​​​

  const SpeedMeasurePlugin = require('speed-measure-webpack-plugin');  const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;+ const HardSourceWebpackPlugin = require('hard-source-webpack-plugin');module.exports = {  ...,  configureWebpack: (config) => {      ...      config.plugins.push(        // 为模块提供中间缓存,缓存路径是:node_modules/.cache/hard-source        // 解决未检测到的配置更改       + new HardSourceWebpackPlugin({          root: process.cwd(),          directories: [],          environmentHash: {            root: process.cwd(),            directories: [],            // 配置了files 的主要原因是解决配置更新,cache 不生效了的问题            // 配置后有包的变化,plugin 会重新构建一部分 cache            files: ['package.json', 'yarn.lock']          }        }),        new SpeedMeasurePlugin(),        new BundleAnalyzerPlugin(),      );    },};
 

使用 hard-source-webpack-plugin 后的 "冷启动" 和 "二次启动" 时间如下:

图片

图片

图片

减少打包体积

减少 js 代码体积

在打包之后的 dist 目录下,查找对应的 console.log,结果如下:

图片

可以发现 vue-cli 中默认的配置并没有将 js 文件中的一些 console.log 语句进行删除,因此这也是一些可以继续优化的内容.

这里可以通过 uglifyjs-webpack-plugin terser-webpack-plugin 插件来删除注释和压缩 js 代码,具体配置可以直接点链接进行查阅.

对图片压缩

针对一些对图片像素没有很高要求的图片资源进行压缩,这里可以通过 image-webpack-pluginimage-minimizer-webpack-plugin 进行图片资源的压缩,具体配置可以直接点链接进行查阅.

外部扩展 —— externals & cdn

externals 选项就是用于 防止 将某些 import 的包(package) 打包到 bundle 中,而是在运行时(runtime) 再去从外部获取这些 扩展依赖(external dependencies),具体在 webpack 文档中可见

简单来说就是原本应该要被打包到 bundle 中的 js,现在通过配置 externals 选项,将它做外 bundle 之外的资源,即 cdn 资源,在代码运行时再去请求这个资源。

例如,下面就是在项目关于 externals 的配置:

​​​​​​​

 chainWebpack: (config) => {    ...    // 通过 CDN 方式引入资源      config.externals({        echarts: 'echarts',        nprogress: 'NProgress',      }); }
 

DllPlugin 插件 —— 优化打包时间

PS: webpack4或以上的版本,vue官方 和 react 官方 都已不推荐使用此插件DllPlugin 插件负责将那些比较稳定(例如 vue/react 全家桶)的库进行打包拆分 bundles,下次在打包时就不需要重复进行打包,因此大幅度提升了构建的速度。

webpack4 版本中,已经集成 DllPlugin 插件,我们只需要进行配置即可,具体可见

  • 创建 dll.js 文件,进行简单配置​​​​​​​

const path = require('path');const webpack = require('webpack');
module.exports = {  entry: {    vendor: ['echarts', 'element-ui', 'vue/dist/vue.esm.js', 'vue-router', 'vuex'],  },  output: {    path: path.join(__dirname, 'target'),    filename: '[name].js',    library: '[name]_[hash]',  },  plugins: [    new webpack.DllPlugin({      // DllPlugin的name属性需要和libary保持一致      name: '[name]_[hash]',      //指定当前目录      path: path.join(__dirname, '.', '[name]-manifest.json'),      // context需要和webpack.config.js保持一致      context: __dirname,    }),  ],};
 
  • package.json 文件中配置 script 脚本:"dll": "webpack --config ./dll.js"

  • 安装 webpack-cli,因为原本依赖中并没有安装过这个依赖,而运行这个脚本命令需要 webpack-cli

  • 执行脚本命令 npm run dll,生成 vendor-manifest.json 文件,这个文件是用于让 DllReferencePlugin 能够映射到相应的依赖上

图片

  • vue.config.js 中配置 DllReferencePlugin 插件,链接到已被打包的依赖上

​​​​​​​
const { pathResolve } = require('./build/utils.js'); // eslint-disable-lineconst devConfig = require('./build/webpack.dev.conf.js'); // eslint-disable-lineconst buildConfig = require('./build/webpack.prod.conf.js');// 分析工具const SpeedMeasurePlugin = require('speed-measure-webpack-plugin');const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
// 资源缓存const HardSourceWebpackPlugin = require('hard-source-webpack-plugin');
// 抽离稳定的第三方库,避免重复打包const DllReferencePlugin = require('webpack').DllReferencePlugin;
// 公共函数const { versionSet } = require('./build/utils'); // eslint-disable-line
// 是否为开发环境const isDevelopment = process.env.NODE_ENV == 'development';
const vueWebpackConfig = () => {  let envConfig = {};
  if (isDevelopment) {    // 开发    envConfig = devConfig;  } else {    // 构建    versionSet();    envConfig = buildConfig;  }
  const vueConfig = {    // 环境配置    ...envConfig,    productionSourceMap: isDevelopment, // 是否在构建生产包时生成sourcdeMap
    // 拓展webpack配置    chainWebpack: (config) => {      //  ============ 配置别名 ============      config.resolve.alias        .set('@build', pathResolve('../build')) // 构建目录        .set('@', pathResolve('../src'))        .set('@api', pathResolve('../src/api'))        .set('@utils', pathResolve('../src/utils'))        .set('@views', pathResolve('../src/views'));
      // ============ svg处理 ============      const svgRule = config.module.rule('svg');      // 清除已有的所有 loader。      // 如果你不这样做,接下来的 loader 会附加在该规则现有的 loader 之后。      svgRule.uses.clear();
      // 添加要替换的 loader      svgRule.use('svg-sprite-loader').loader('svg-sprite-loader').options({        symbolId: 'icon-[name]',      });
      // ============ 压缩图片 ============      config.module        .rule('images')        .use('image-webpack-loader')        .loader('image-webpack-loader')        .options({ bypassOnDebug: true })        .end();
      // ============ 打包分析工具 ============      if (!isDevelopment) {        if (process.env.npm_config_report) {          config.plugin('webpack-bundle-analyzer').use(require('webpack-bundle-analyzer').BundleAnalyzerPlugin).end();          config.plugins.delete('prefetch');        }      }
      // ============ CDN资源引入 ============      config.externals({        // echarts: 'echarts',        nprogress: 'NProgress',      });    },
    configureWebpack: (config) => {      // 尽量保证项目中文件后缀的精确      config.resolve.extensions = ['.ts', '.js', '.vue', '.json'];
      // 处理 babel-loader      config.module.rules[12].use.unshift({        loader: 'thread-loader',      });
      config.plugins.push(        // 为模块提供中间缓存,缓存路径是:node_modules/.cache/hard-source        new HardSourceWebpackPlugin({          root: process.cwd(),          directories: [],          environmentHash: {            root: process.cwd(),            directories: [],            // 配置了files 的主要原因是解决配置更新,cache 不生效了的问题,配置后有包的变化,plugin 会重新构建一部分cache            files: ['package.json', 'yarn.lock'],          },        }),
        // DllReferencePlugin 插件          new DllReferencePlugin({            context: __dirname,            // manifest就是我们第 2 步中打包出来的 json 文件            manifest: require('./vendor-manifest.json'),          }),
        // 分析工具        new SpeedMeasurePlugin(),        new BundleAnalyzerPlugin(),      );    },  };
  return vueConfig;};
module.exports = vueWebpackConfig();
 

其他优化

resolve.alias & resolve.extensions

resolve.alias 是用于创建 import 或 require 的别名,来确保模块引入变得更简单

例如,下面是项目中定义的一些别名:

​​​​​​​​​​​​​​

   chainWebpack: (config) => {      // 配置别名      config.resolve.alias        .set('@build', pathResolve('../build')) // 构建目录        .set('@', pathResolve('../src'))        .set('@api', pathResolve('../src/api'))        .set('@utils', pathResolve('../src/utils'))        .set('@views', pathResolve('../src/views'));  }
 

resolve.extensions 指定为对应的文件后缀,保证在查找模块时的无用查找和递归等,即保证这个配置里的文件后缀要尽可能少。

图片

图片

减少不必要的解析 —— module.noParse

module.noParse 是防止 webpack 解析那些任何与给定正则表达式相匹配的文件,忽略的文件中 不应该含有 importrequiredefine 的调用,或任何其他导入机制,通过忽略大型的 library 可以提高构建性能。

例如,vue-cli 中关于 module.noParse 的配置如下:

图片

代码层面优化

webpack-bundle-analyzer 反应的内容,可以发现某些 jscss 模块体积相对来说比较大,这个时候可以找到对应的文件梳理逻辑并进行代码优化,如封装 js 逻辑、抽离 css 样式等,即最基本的优化就是少书写重复的样式和逻辑,这样也能避免一些无效的重复编译,这里就不在额外扩展了,因为这部分算是老生常谈了:在 Vue 和 React 中怎么做优化?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值