Vue-Cli优化编译速度

2022-09-27 15:51:57 浏览数 (1)

前言

Vue-Cli中内置了Webpack,但是配置文件和Webpack也不尽相同。

我们可以通过命令查看对应的Webpack配置。

对于优化主要是两个方面

  • 构建速度
  • 打包体积

所以不管是分析问题还是解决问题有围绕这连个方面进行处理。

Vue-Cli自带

  • cache-loader 会默认为 Vue/Babel/TypeScript 编译开启。文件会缓存在 node_modules/.cache 中。 如果你遇到了编译方面的问题,记得先清缓存目录之后再试试看。
  • thread-loader 会在多核 CPU 的机器上为 Babel/TypeScript 转译开启。

查看Vue-Cli中的Webpack配置

介绍

Vue-Cli脚手架会有webpack的很多默认行为,因此我们得知道基于Vue-Cli的项目,当前的webpack都配置了啥,然后才能做针对性的分析与优化。

vue-cli-service 暴露了 inspect 命令用于审查解析好的 webpack 配置。那个全局的 vue 可执行程序同样提供了 inspect 命令,这个命令只是简单的把 vue-cli-service inspect 代理到了你的项目中。

使用方式:

代码语言:javascript复制
#根据mode,分别生成开发环境、生产环境的配置
vue inspect --mode production > webpack.config.production.js
vue inspect --mode development > webpack.config.development.js

输入命令后,在根目录会生产一个webpack.config.production.js文件

如果vue command not found的错可以全局安装注册一下vue命令

代码语言:javascript复制
npm install -g vue-cli

分析

查看构建时间

说明:https://www.npmjs.com/package/speed-measure-webpack-plugin

安装

代码语言:javascript复制
npm install --save-dev speed-measure-webpack-plugin

使用

代码语言:javascript复制
const SpeedMeasurePlugin = require('speed-measure-webpack-plugin')
module.exports = {
  chainWebpack: config => {
    config
      .plugin('speed-measure-webpack-plugin')
      .use(SpeedMeasurePlugin)
      .end();
  }
}

或者

代码语言:javascript复制
const SpeedMeasurePlugin = require('speed-measure-webpack-plugin');
module.exports = {
  configureWebpack: (config) => {
    config.plugins.push(
      new SpeedMeasurePlugin(),
    );
  },
};

查看构建库大小

VUE CLI内置工具

代码语言:javascript复制
vue-cli-service build --report

成功后就会在项目目录下找到/dist/report.html

结果如下图所示:

如果报错

node_modules.binvue-cli-service.ps1,因为在此系统上禁止运行脚本。

执行之后就能运行上面的命令了

代码语言:javascript复制
set-ExecutionPolicy RemoteSigned -Scope CurrentUser
#查看执行权限
Get-ExecutionPolicy

webpack-bundle-analyzer

Vue CLi就不用这个工具了,但是也可以配置,配置后运行项目打开项目页面的同时也会打开分析页面。

安装

代码语言:javascript复制
npm install --save-dev webpack-bundle-analyzer

配置

Webpack配置

代码语言:javascript复制
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
  plugins: [
    new BundleAnalyzerPlugin()
  ]
}

Vue Cli配置

代码语言:javascript复制
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
  configureWebpack: {
    plugins: [
      new BundleAnalyzerPlugin()
    ]
  }
}

优化

构建速度优化

happypack

安装

代码语言:javascript复制
npm install --save-dev happypack

配置

代码语言:javascript复制
/*
happypack
*/
const HappyPack = require('happypack');
const os = require('os');
const happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length });

module.exports = {
  configureWebpack: config => {
    return {
      /* happypack */
      module: {
        rules: [
          {
            test: /.js$/,
            loader: 'happypack/loader?id=happyBabel',
            exclude: /node_modules/
          },
        ]
      },
      externals: {
        vue: "Vue",
        "vue-router": "VueRouter",
        vuex: "Vuex",
        echarts: "echarts",
        axios: "axios",
      },
      plugins: [
        new HappyPack({
          id: 'happyBabel',
          loaders: [{
            loader: 'babel-loader?cacheDirectory=true',
          }],
          //共享进程池
          threadPool: happyThreadPool,
          //允许 HappyPack 输出日志
          verbose: true,
        })
      ],
    }

  },
}

实测没啥效果

vue-cli-plugin-dll

代码语言:javascript复制
npm install --save-dev vue-cli-plugin-dll

接下来就是dll的相关配置,将我们项目中的依赖使用dll插件进行动态链接,这样依赖就不会进行编译,从而极大地提高编译速度,因为这些插件没有编译,在vue.config.js中进行配置,也很简单

代码语言:javascript复制
const path = require("path");
module.exports = {
  pluginOptions: {
    dll: {
      //这里放的是你的依赖插件,就是你项目安装的其他的插件,将这些插件的名字依次加在后面,我建议将所有项目依赖插件全部放在后面
      //注意这里不能放webpack,gulp等需要node环境的插件,我尝试将babel等放到这里报错提示没有V8环境
      entry: ["vue", "vue-router", "view-design"],
      //dll 编译后的链接库的地址
      cacheFilePath: path.resolve(__dirname, "./public"),
      // 是否开启 DllReferencePlugin,
      open: true,
      // 在执行 `dev` , `build` 等其他指令时,程序会自动将 `dll` 指令生成的 `*.dll.js` 等文件自动注入到 index.html 中。
      inject: true
    }
  },
}

多入口

代码语言:javascript复制
const path = require('path')
module.exports = {
  pluginOptions: {
    dll: {
      // 入口配置
      entry: {
        vue: ["vue", "vue-router", "vuex"],
        ui: ["view-design"],
      },
      // 输出目录
      output: {
        path: path.join(__dirname, 'public/dll'),
        filename: '[name].dll.js',
        // vendor.dll.js中暴露出的全局变量名
        // 保持与 webpack.DllPlugin 中名称一致
        library: '[name]_[hash]'
      },
      // 是否开启 DllReferencePlugin,
      open: true,

      // 在执行 `dev` , `build` 等其他指令时,程序会自动将 `dll` 指令生成的 `*.dll.js` 等文件自动注入到 index.html 中。
      inject: true,
    }
  }
}

配置好之后然后运行,进行你上面配置插件动态链接库的编译

代码语言:javascript复制
npx vue-cli-service dll

dll编译完成后会在上面配置的目录下生成dll文件夹,就可以开始跑项目了,因为这些插件都不需要编译,跑起来很流畅,修改后的热更新速度更是显著提升。我以前修改一行代码热更新编译在30秒以上,使用这个以后基本十秒以内搞定。

代码语言:javascript复制
npm run serve

多线程优化

Vue-Cli

Vue-Cli已经内置,开启

代码语言:javascript复制
module.exports = {
  parallel: true,
}

parallel

  • Type: boolean
  • Default: require('os').cpus().length > 1 是否为 Babel 或 TypeScript 使用 thread-loader。 该选项在系统的 CPU 有多于一个内核时自动启用,仅作用于生产构建。
Webpack
代码语言:javascript复制
npm install --save-dev thread-loader

配置

代码语言:javascript复制
const path = require("path");
module.exports = {
  module: {
    rules: [
      {
        test: /.js$/,
        include: path.resolve('src'),
        use: [
          "thread-loader",
          // 耗时的 loader (例如 babel-loader)
        ],
      },
    ],
  },
};

缓存构建

Webpack 中几种缓存方式:

  • cache-loader
  • hard-source-webpack-plugin

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

Vue-Cli 已经内置了 cache-loader 进行以下两个的缓存了

  • babel-loader 的 cacheDirectory 标志
  • vue-loader 的 cacheDirectory 标志

所以

Vue Cli没有必要添加HardSourceWebpackPlugin

HardSourceWebpackPlugin

详细说明

https://www.npmjs.com/package/hard-source-webpack-plugin

在启动项目时会针对项目生成缓存,若是项目无package或其他变化,下次就不用花费时间重新构建,直接复用缓存。

安装

代码语言:javascript复制
npm install --save-dev hard-source-webpack-plugin

配置vue.config.js

为模块提供中间缓存,缓存路径是:node_modules/.cache/hard-source

代码语言:javascript复制
const HardSourceWebpackPlugin = require('hard-source-webpack-plugin')
module.exports = {
  configureWebpack: config => {
    config.plugin.push(
      // 为模块提供中间缓存,缓存路径是:node_modules/.cache/hard-source
      new HardSourceWebpackPlugin({
        root: process.cwd(),
        directories: [],
        environmentHash: {
          root: process.cwd(),
          directories: [],
          files: ['package.json', 'yarn.lock']
        }
      })
      // 配置了files的主要原因是解决配置更新,cache不生效了的问题,配置后有包的变化,plugin会重新构建一部分cache
    )
  }
}

或者

代码语言:javascript复制
const HardSourceWebpackPlugin = require('hard-source-webpack-plugin')
module.exports = {
  configureWebpack: {
    plugins:[
      new HardSourceWebpackPlugin({
        root: process.cwd(),
        directories: [],
        environmentHash: {
          root: process.cwd(),
          directories: [],
          files: ['package.json', 'yarn.lock']
        }
      })
    ]
  }
}

更多配置

代码语言:javascript复制
// 缓存 加速二次构建速度
new HardSourceWebpackPlugin({
  cacheDirectory: "../node_modules/.cache/hard-source/[confighash]",
  recordsPath:
  "../node_modules/.cache/hard-source/[confighash]/records.json",
  configHash: function(webpackConfig) {
    return require("node-object-hash")({ sort: false }).hash(
      webpackConfig
    );
  },
  // Either false, a string, an object, or a project hashing function.
  environmentHash: {
    root: process.cwd(),
    directories: [],
    files: ["../package-lock.json", "../yarn.lock"]
  },
  // An object.
  info: {
    // 'none' or 'test'.
    mode: "none",
    // 'debug', 'log', 'info', 'warn', or 'error'.
    level: "debug"
  },
  // Clean up large, old caches automatically.
  cachePrune: {
    // Caches younger than `maxAge` are not considered for deletion. They must
    // be at least this (default: 2 days) old in milliseconds.
    maxAge: 2 * 24 * 60 * 60 * 1000,
    // All caches together must be larger than `sizeThreshold` before any
    // caches will be deleted. Together they must be at least this
    // (default: 50 MB) big in bytes.
    sizeThreshold: 100 * 1024 * 1024
  }
})

缩小文件检索解析范围

为避免无用的检索与递归遍历,可以使用alias指定引用时候的模块,noParse,对不依赖本地代码的第三方依赖不进行解析。

Vue-Cli默认已进行了如下配置

代码语言:javascript复制
noParse: /^(vue|vue-router|vuex|vuex-router-sync)$/

配置

代码语言:javascript复制
// 定义getAliasPath方法,把相对路径转换成绝对路径
const getAliasPath = dir => join(__dirname, dir)
module.exports = {
	configureWebpack: config => {
    config.module.noParse = /^(vue|vue-router|vuex|vuex-router-sync|lodash|echarts|axios|view-design)$/
  }
  chainWebpack: config => {
    // 添加别名
    config.resolve.alias
      .set('@', getAliasPath('src'))
      .set('assets', getAliasPath('src/assets'))
      .set('utils', getAliasPath('src/utils'))
      .set('views', getAliasPath('src/views'))
      .set('components', getAliasPath('src/components'))
	}
  // 生产环境禁用eslint
  lintOnSave: !process.env.NODE_ENV !== 'production',
}

或者

代码语言:javascript复制
// vue.config.js
module.exports = {
   configureWebpack:{
    module: {
      noParse: /^(vue|vue-router|vuex|vuex-router-sync|lodash|echarts|axios|view-design)$/
    }
  }
}

import优化

运用这个插件能在代码使用了import语法的情况下,大大提高代码的编译速度。

安装 babel-plugin-dynamic-import-node

代码语言:javascript复制
npm install --save-dev babel-plugin-dynamic-import-node

vue-cli3

修改babel.config.js文件

代码语言:javascript复制
module.exports = {
  presets: ["@vue/cli-plugin-babel/preset"],
  env: {
    development: {
      plugins: ["dynamic-import-node"]
    }
  }
};

vue.cli2

.babelrc文件

代码语言:javascript复制
"env": {
        "test": {
                 "plugins": []
                },
        "development":{
                       "presets": ["env", "stage-2"],
                       "plugins": ["dynamic-import-node"]
                      }
       }

打包体积优化

不生成SourceMap

production环境不生成SourceMap

代码语言:javascript复制
module.exports = {
  lintOnSave: false,
  productionSourceMap: process.env.NODE_ENV !== "production", //打包不生成map文件
}

图片压缩

image-webpack-plugin

对图片像素要求没很极致的,这个压缩还是可以使用的,压缩率肉眼看起来感觉是没太大区别。 这里注意一下,我没有对svg进行压缩,原因是压缩的svg,再通过构建时被打包成base64时,生成的base64会有问题,无法访问。

代码语言:javascript复制
module.exports = {
  chainWebpack: config => {
   // 对图片进行压缩
    config.module
      .rule('images')
      .test(/.(png|jpe?g|gif)(?.*)?$/)
      .use('image-webpack-loader')
      .loader('image-webpack-loader')
      .options({ bypassOnDebug: true })
      .end()
	}
}

gzip压缩

使用 gzip 压缩代码,效果显著。

安装

代码语言:javascript复制
npm install compression-webpack-plugin

配置

代码语言:javascript复制
const CompressionWebpackPlugin = require('compression-webpack-plugin')

module.exports = {
  configureWebpack: config => {
    // 生产环境下生效
    if (process.env.NODE_ENV === 'production') {
      // 配置 gzip 压缩
      config.plugins.push(
        new CompressionWebpackPlugin({
          test: /.js$|.html$|.css$/,
          threshold: 4096 // 超过4kb压缩
        })
      )
    }
  }
}

配置CDN

老实说,我不用这个功能的,线上使用 cdn 总让我有一种不安全感,除非公司有自己的 cdn 库,不过这确实也是一种优化方案,效果也还不错。它的配置也很简单,在 externals 中配置,例子:

代码语言:javascript复制
module.exports = {
  configureWebpack: config => {
    if (process.env.NODE_ENV === 'production') {
      // 配置 cdn,这里将 vue,vue-router 和 axios 三个包配置成 cdn 引入
      // 其中 Vue,VueRouter 等名称是该库暴露在全局中的变量名
      config.externals = {
        vue: 'Vue',
        'vue-router': 'VueRouter',
        axios: 'axios'
      }
    }
  }
}

然后在 public/index.html 模板文件中引入 cdn 地址:

代码语言:javascript复制
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width,initial-scale=1.0" />
    <link rel="icon" href="<%= BASE_URL %>favicon.ico" />
    <title></title>
    <!-- 引入 cdn 地址 -->
    <script src="https://cdn.bootcdn.net/ajax/libs/vue/2.5.10/vue.min.js"></script>
    <script src="https://cdn.bootcdn.net/ajax/libs/vue-router/3.0.1/vue-router.min.js"></script>
    <script src="https://cdn.bootcdn.net/ajax/libs/axios/0.18.0/axios.min.js"></script>
  </head>
  <body>
    <div id="app"></div>
  </body>
</html>

我这里使用的是 bootcdn 的地址,需要注意版本问题。

也可以借助 HtmlWebpackPlugin 插件来方便插入 cdn 的引入。

使用 cdn 引入的方式虽然能极大改善网页加载速度,但我还是不会用这个功能,项目还不需要非得这样的优化,也怕 cdn 不稳定。

当然

也可以不用CDN,直接把JS复制到项目下,用相对路径引用即可。

借助 HtmlWebpackPlugin 插件来方便插入 cdn 的引入

代码语言:javascript复制
//生产环境标记
const IS_PRODUCTION = process.env.NODE_ENV === "production";
const path = require("path");
// 生产配置
const cdn_production = {
  js: ["/librarys/vue@2.6.11/vue.min.js"]
};
// 开发配置
const cdn_development = {
  js: ["/librarys/vue@2.6.11/vue.js"]
};

module.exports = {
  configureWebpack: {
    externals: {
      vue: "Vue",
    },
  },
  chainWebpack: config => {
    config.plugin("html").tap(args => {
      args[0].cdn = IS_PRODUCTION ? cdn_production : cdn_development;
      return args;
    });
  }
};

index.html中添加

代码语言:javascript复制
<% for (var i in htmlWebpackPlugin.options.cdn&&htmlWebpackPlugin.options.cdn.js) { %>
  <script src="<%= htmlWebpackPlugin.options.cdn.js[i] %>"></script>
<% } %>

Vue Cli配置说明

https://cli.vuejs.org/zh/guide/webpack.html

简单的配置方式

调整 webpack 配置最简单的方式就是在 vue.config.js 中的 configureWebpack 选项提供一个对象:

代码语言:javascript复制
// vue.config.js
module.exports = {
  configureWebpack: {
    plugins: [
      new MyAwesomeWebpackPlugin()
    ]
  }
}

该对象将会被 webpack-merge 合并入最终的 webpack 配置。

链式操作 (高级)

Vue CLI 内部的 webpack 配置是通过 webpack-chain 维护的。这个库提供了一个 webpack 原始配置的上层抽象,使其可以定义具名的 loader 规则和具名插件,并有机会在后期进入这些规则并对它们的选项进行修改。

它允许我们更细粒度的控制其内部配置。接下来有一些常见的在 vue.config.js 中的 chainWebpack 修改的例子。

提示

当你打算链式访问特定的 loader 时,vue inspect 会非常有帮助。

修改 Loader 选项

代码语言:javascript复制
// vue.config.js
module.exports = {
  chainWebpack: config => {
    config.module
      .rule('vue')
      .use('vue-loader')
      .tap(
      options => {
        // 修改它的选项...
        return options
      })
  }
}

提示

对于 CSS 相关 loader 来说,我们推荐使用 css.loaderOptions 而不是直接链式指定 loader。这是因为每种 CSS 文件类型都有多个规则,而 css.loaderOptions 可以确保你通过一个地方影响所有的规则。

0 人点赞