【项目升级】将vue-cli2.9升至4.5

一、为什么要升级

公司的项目都是采用vue-cli2.9构建的,随着业务的复杂和项目的庞大,很多优化项无法达到预期的要求。在尝试各种方法之后,最终决定升级到cli4.0版本。

4.0版本带来的变化有以下几点:

  • 采用webpack 4.x构建,提升启动和打包效率;
  • webpack 4.x 使用的 splitChunks,更好的拆分代码,减小文件体积;
  • 自带的tree-shaking,减少不必要的引入;
  • 拥抱社区,与时俱进。

二、安装,创建项目

1. 安装

这一步按照 vue-cli官方文档 安装即可

Vue CLI 的包名称由 vue-cli 改成了 @vue/cli。 如果你已经全局安装了旧版本的 vue-cli (1.x 或 2.x),你需要先通过 npm uninstall vue-cli -g 或 yarn global remove vue-cli 卸载它。
Vue CLI 4.x 需要 Node.js v8.9 或更高版本 (推荐 v10 以上)。你可以使用 n,nvm 或 nvm-windows 在同一台电脑中管理多个 Node 版本。

可以使用下面的命令进行安装:

  npm install -g @vue/cli
  # OR
  yarn global add @vue/cli

安装之后,你就可以在命令行中访问 vue 命令。你可以通过简单运行 vue,看看是否展示出了一份所有可用命令的帮助信息,来验证它是否安装成功。

你还可以用这个命令来检查其版本是否正确(4.x):

  vue --version
  # OR
  vue -V

2. 创建项目

参考 官方文档 创建一个新项目

运行以下命令进行创建:

  vue create my-project

这里采用自定义创建,把我们项目需要的都勾选上

创建项目

三、配置eslint

eslint 是每个项目必不可少的,可以尽早发现在编写过程中出现的错误,统一团队编写规范。如果使用vscode,可以配置插件,保存时按照eslint规则自动格式化

在上一步创建的过程中已经添加了eslint了,接下来就是配置规则,下面是我的一些规则,供参考:

.eslintrc.js中配置,参考 vscode里面怎么根据eslint来格式化代码?

module.exports = {
  root: true,
  env: {
    node: true
  },
  extends: ['plugin:vue/essential', 'eslint:recommended', 'plugin:vue/strongly-recommended'],
  parserOptions: {
    parser: 'babel-eslint'
  },
  plugins: [
    'vue'
  ],
  rules: {
    // "no-console": process.env.NODE_ENV === "production" ? "error" : "off",
    'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',

    'vue/no-side-effects-in-computed-properties': 'off',
    // 要求构造函数首字母大写 (要求调用 new 操作符时有首字母大小的函数,允许调用首字母大写的函数时没有 new 操作符。)
    'new-cap': [2, { 'newIsCap': true, 'capIsNew': false }],
    // 不能有声明后未被使用的变量或参数
    'no-unused-vars': 0,
    // 强制 generator 函数中 * 号周围使用一致的空格
    'generator-star-spacing': [2, { 'before': true, 'after': true }],
    // 允许在开发环境使用debugger
    'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
    // 允许在开发环境使用console
    'no-console': 'off',
    // 关闭语句强制分号结尾
    'semi': [2, 'always'],
    // 空行最多不能超过100行
    // "no-multiple-empty-lines": [0, {"max": 100}],
    // 关闭禁止混用tab和空格
    // 'no-mixed-spaces-and-tabs': 2,
    //   "indent": [2, 4], //缩进风格
    //   "vue/script-indent": [2, 4, {
    //     baseIndent: 1
    //   }],
    // 强制在 function的左括号之前使用一致的空格
    'space-before-function-paren': [2, 'always'],
    'quotes': [1, 'single'],
    // 'comma-dangle': [1, 'always-multiline']
    'import/no-unresolved': [0],
    // 'indent': 0,
    // 禁用不必要的转义字符
    'no-useless-escape': 2,
    // prop 的默认值必须匹配它的类型
    'vue/require-valid-default-prop': 'off',
    'vue/html-closing-bracket-newline': 'off',
    'vue/no-dupe-keys': 'off',
    'vue/no-template-shadow': 'off'
  }
}

再添加一个忽略选项,让插件和node_modules中的代码不需要eslint检查

添加 .eslintignore 文件

/config/
/dist/
/src/plugin/
/node_modules/
/public/

四、项目迁移

1. 迁移 package.json

项目创建好之后,接下来就是把原来项目中package.json 中的 dependencies 下的依赖迁移到新项目,这里建议直接拷贝过来,再执行 npm install 下载即可。如果下载最新的版本,可能导致项目中某些功能异常。

2. 迁移 staticpublic 目录下

老项目的 static 全部迁移到 public 文件夹中,index.html 迁移到 public

在 vue-cli3以上的版本中,public 文件夹不会被 webpack 处理,并部署在网站根目录。

对于publick的一些说明,参考 官方文档

3. 复制老项目的 src 目录替换新项目的 src

将老项目的src目录整体迁移过来,这时可能路径暂时还对应不上,接下来就需要配置一些快捷路径

4. 创建 vue.config.js 文件

这里面的配置非常多,参考 官网 的全部配置项

我们只配置一些项目启动的必须项

const path = require('path');

const resolve = (dir) => {
  return path.join(__dirname, dir);
}
module.exports = {
  publicPath: '/', // 项目基本路径,对应 BASE_URL
  // lintOnSave: false,
  outputDir: 'news', // 打包后的文件夹名,默认 dist
  productionSourceMap: false, // 是否开启sourceMap
  devServer: { // 本地启动项
    proxy: {} // 接口代理
  },
  css: {
    extract: true,
    sourceMap: false,
    requireModuleExtension: true,
    loaderOptions: {
      sass: { // 全局引入sass变量和函数
        prependData: `
          @import "~@/assets/style/vars.sass"
          @import "~@/assets/style/mixin.sass"
        `
      },
      scss: { // 全局引入scss变量
        prependData: `
          @import "~@/assets/style/vars.sass";
          @import "~@/assets/style/mixin.sass";
        `
      }
    }
  },
  chainWebpack: config => {
    config
      .resolve.alias // 快捷访问路径
      .set('@', resolve('src'))
      .set('http', resolve('src/http/axios.js'))
      .set('@common', resolve('src/common'))
      .set('@util', resolve('src/assets/js/util.js'));
    config // 设置url-loader
      .module
      .rule('images')
      .use('url-loader')
      .loader('url-loader')
      .tap(options => Object.assign(options, { limit: 1000 }));
    return config;
  }
};

此时执行 npm run serve 就可以正常启动项目了

项目优化

1. 开启gzip

gzip 可以减少文件的大小,且兼容性也好,参考:探索HTTP传输中gzip压缩的秘密

安装插件:

npm install -D compression-webpack-plugin

安装之后在vue-config.js 中引入插件

module.exports = {
// ...
  configureWebpack: (webpackConfig) => {
    if (process.env.NODE_ENV === 'production') {
      return {
        plugins: [
          new CompressionPlugin({
            test:  /.js$|.html$|.css$|.jpg$|.jpeg$|.png/, // 需要压缩的文件类型
            threshold: 10240, // 归档需要进行压缩的文件大小最小值,当前设置10K
            deleteOriginalAssets: false // 是否删除原文件
          })
        ]
      };
    }
  }
//...

2. 拆分代码

webpack-cli4 基于webpack 4 进行构建,因此可以利用webpacksplitChunks进行代码拆分

module.exports = {
  optimization: {
    splitChunks: {
      chunks: 'async',//表示将选择哪些块进行优化。提供字符串时,有效值为all、async和initial,默认是仅对异步加载的块进行分割。
      minSize: 30000,//模块大于minSize时才会被分割出来。
      maxSize: 0,//生成的块的最大大小,如果超过了这个限制,大块会被拆分成多个小块。
      minChunks: 1,//拆分前必须共享模块的最小块数。
      maxAsyncRequests: 5,//按需加载时并行请求的最大数目。
      maxInitialRequests: 4,//入口点的最大并行请求数
      automaticNameDelimiter: '~',//默认情况下,webpack将使用块的来源和名称(例如vendors~main.js)生成名称。此选项允许您指定要用于生成的名称的分隔符。
      automaticNameMaxLength: 30,//允许为SplitChunksPlugin生成的块名称的最大长度
      cacheGroups: {
        cacheGroups: {
         swiper: {
            name: 'chunk-swiper',
            test: /[\/]node_modules[\/]swiper[\/]/,  //控制此缓存组选择的模块。省略它将选择所有模块。它可以匹配绝对模块资源路径或块名称。匹配块名称时,将选择块中的所有模块。
            chunks: 'all',
            priority: 5,
            reuseExistingChunk: true,
            enforce: true
          },
          vant: {
            name: 'chunk-vant',
            test: /[\/]node_modules[\/]vant[\/]/,
            chunks: 'all',
            priority: 5,
            reuseExistingChunk: true,
            enforce: true
          },
          videojs: {
            name: 'chunk-videojs',
            test: /[\/]node_modules[\/](video.js)|(videojs-contrib-hls)[\/]/,
            chunks: 'all',
            priority: 5,
            reuseExistingChunk: true,
            enforce: true
          },
          utils: {
            name: 'chunk-utils',
            test: /[\/]node_modules[\/](crypto-js)|(md5.js)|(core-js)|(axios)[\/]/,
            chunks: 'all',
            priority: 5,
            reuseExistingChunk: true,
            enforce: true
          }
        },
        default: {
          minChunks: 2,
          priority: -20,
          reuseExistingChunk: true//如果当前块包含已经从主包中分离出来的模块,那么该模块将被重用,而不是生成新的模块
        }
      }
    }
  }
};

splitChunks 常用参数:

  • name 打包的 chunks 的名字
  • test 匹配到的模块奖杯打进这个缓存组
  • chunks 代码块类型 必须三选一: “initial”(初始化) | “all”(默认就是 all) | “async”(动态加载)默认 Webpack 4 只会对按需加载的代码做分割。如果我们需要配置初始加载的代码也加入到代码分割中,可以设置为 ‘all’
  • priority :缓存组打包的先后优先级,数值大的优先
  • minSize (默认是30000)形成一个新代码块最小的体积
  • minChunks (默认是1)在分割之前,这个代码块最小应该被引用的次数
  • maxInitialRequests (默认是3)一个入口最大的并行请求数
  • maxAsyncRequests(默认是5)按需加载时候最大的并行请求数
  • reuseExistingChunk 如果当前的 chunk 已被从 split 出来,那么将会直接复用这个 chunk 而不是重新创建一个
  • enforce 告诉 webpack 忽略 splitChunks.minSize, splitChunks.minChunks, splitChunks.maxAsyncRequests and splitChunks.maxInitialRequests,总是为这个缓存组创建 chunks

3. 配置 analyzer,分析打包后的文件

下载插件:

npm install -D webpack-bundle-analyzer

vue.config.js中引入插件

const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

module.exports = {
  configureWebpack: (webpackConfig) => {
    if (process.env.NODE_ENV === 'production') {
        return {
          plugins: [
            new BundleAnalyzerPlugin()
          ],
        }
    }
  }
}

package.json 中配置script

{
  "scripts": {
    "analyz": "npm_config_report=true npm run build"
  }
}

执行 npm run analyz 就能帮我们分析包的大小了

打包后文件分析

4. 将一些配置抽成可配项,单独存放一个文件,类似于 cli2.9 中的config

在根目录下新建 config/index.js

module.exports = {
  publicPath: `/`,
  outputDir: 'news',
  htmlTitle: '方正翔宇',
  dev: {
    proxyTable: {
      '/app_if': {
        target: 'http://api-xiangyu.test1.fzyun.cn/',
        changeOrigin: true,
        pathRewrite: {
          // '^/app_if': ''
        }
      },
    }
  },
  build: {
    // 是否开启sourceMap
    productionSourceMap: false,
    // 是否开启gzip压缩
    productionGzip: false,
    productionGzipExtensions: ['js', 'css'],
    // npm run build --report
    // Set to `true` or `false` to always turn it on or off
    bundleAnalyzerReport: process.env.npm_config_report
  }
};

vue.config.js 完整配置

const path = require('path');
const config = require('./config');
const CompressionPlugin = require('compression-webpack-plugin');
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

const resolve = (dir) => {
  return path.join(__dirname, dir);
}

const htmlTitle = config.htmlTitle;
module.exports = {
  publicPath: config.publicPath,
  // lintOnSave: false,
  outputDir: config.outputDir,
  productionSourceMap: config.build.productionSourceMap,
  devServer: {
    proxy: config.dev.proxyTable || {}
  },
  lintOnSave: 'default',
  configureWebpack: (webpackConfig) => {
    if (process.env.NODE_ENV === 'production') {
      if (config.build.productionGzip) {
        webpackConfig.plugins.push(
          new CompressionPlugin({
            test:  new RegExp(
                '\.(' +
                config.build.productionGzipExtensions.join('|') +
                ')$'
              ), // 需要压缩的文件类型
            threshold: 10240, // 归档需要进行压缩的文件大小最小值,当前设置10K
            deleteOriginalAssets: false // 是否删除原文件
          })
        )
      }
      if (config.build.bundleAnalyzerReport) {
        webpackConfig.plugins.push(new BundleAnalyzerPlugin())
      }
      return {
        plugins: [],
        optimization: {
          splitChunks: {
            maxInitialRequests: 4,
            cacheGroups: {
              swiper: {
                name: 'chunk-swiper',
                test: /[\/]node_modules[\/]swiper[\/]/,
                chunks: 'all',
                priority: 5,
                reuseExistingChunk: true,
                enforce: true
              },
              vant: {
                name: 'chunk-vant',
                test: /[\/]node_modules[\/]vant[\/]/,
                chunks: 'all',
                priority: 5,
                reuseExistingChunk: true,
                enforce: true
              },
              videojs: {
                name: 'chunk-videojs',
                test: /[\/]node_modules[\/](video.js)|(videojs-contrib-hls)[\/]/,
                chunks: 'all',
                priority: 5,
                reuseExistingChunk: true,
                enforce: true
              },
              utils: {
                name: 'chunk-utils',
                test: /[\/]node_modules[\/](crypto-js)|(md5.js)|(core-js)|(axios)[\/]/,
                chunks: 'all',
                priority: 5,
                reuseExistingChunk: true,
                enforce: true
              }
            }
          }
        }
      };
    }
  },
  css: {
    extract: true,
    sourceMap: false,
    requireModuleExtension: true,
    loaderOptions: {
      sass: {
        prependData: `
          @import "~@/assets/style/vars.sass"
          @import "~@/assets/style/mixin.sass"
        `
      },
      scss: {
        prependData: `
          @import "~@/assets/style/vars.sass";
          @import "~@/assets/style/mixin.sass";
        `
      }
    }
  },
  chainWebpack: config => {
    config
      .resolve.alias
      .set('@', resolve('src'))
      .set('http', resolve('src/http/axios.js'))
      .set('@common', resolve('src/common'))
      .set('@util', resolve('src/assets/js/util.js'));
    config
      .module
      .rule('images')
      .use('url-loader')
      .loader('url-loader')
      .tap(options => Object.assign(options, { limit: 1000 }));
    config
      .plugin('html')
      .tap(args => {
        args[0].title = htmlTitle || '';
        return args;
      });
    return config;
  }
};
原文地址:https://www.cnblogs.com/shenjp/p/14517567.html