vue 创建项目之后,我们需要做什么?

vue 创建项目之后,我们需要做什么?

  • 前言
  • 创建项目
  • 雪碧图(精灵图)
  • 全局注入 scss 变量
  • 打包分离公共库
  • 注入全局变量
  • 删除 console.log
  • 代码分割
  • 配置 CDN
  • 图片压缩
  • IgnorePlugin
  • 代码规范
  • 关于CICD(自动部署)
  • 总结

前言

现在我们做项目基本都是使用脚手架来初始化项目,不得不说脚手架减少了我们很多工作量。但是我们还需要根据项目的特点,对配置文件进行修改,从而使得我们的开发更加方便快速,甚至还可以做一些性能上的优化。本文就总结一下我使用 vue 脚手架创项目之后所做的一些修改。

创建项目

首先看一下我使用 vue 脚手架创建项目时所选的配置。采用的是typescript+vue2+class-component+scss开发模式。
vue 创建项目之后,我们需要做什么?_第1张图片

雪碧图(精灵图)

在项目中,我们会用到很多小图片。这个时候我们需要借助webpack-spritesmith这个插件把这些小图片合并成一张大的精灵图。这样做的好处如下:

  • 减少小图片的请求次数,这也就意味着网络请求的次数减少了,我们只需要请求一张大的图片回来即可。从而达到了性能上的优化。
  • 使用过字体图标的同学应该知道,我们只需要填写对应的类名即可。现在使用雪碧图也是如此,只需要填写对应的类名即可,不需要写background-image这些东西,因为webpack-spritesmith这个插件已经帮我们做好了。而且只要小图片的命名符合规范,对应的类名就可以自动实现hover效果。这样就可以加快我们的开发效率了。

配置步骤如下:

  • 安装webpack-spritesmith
npm i webpack-spritesmith -D
  • 修改vue.config.js
const SpritesmithPlugin = require("webpack-spritesmith");
const path = require("path");
module.exports = {
  configureWebpack: (config) => {
    config.plugins.push(
      new SpritesmithPlugin({
        src: {
          // 小图片的存放路径
          cwd: path.resolve(__dirname, "./src/assets/images/icons"),
          //  后缀名为.png的图片将会被合并成一张大的雪碧图
          glob: "*.png",
        },
        target: {
          // 生成出来的雪碧图的存放路径
          image: path.resolve(__dirname, "./src/assets/images/sprites.png"),
          // 生成出来雪碧图样式存放路径
          css: [path.resolve(__dirname, "./src/assets/css/sprites.scss")],
        },
        apiOptions: {
          // 生成出来的雪碧图相对于生成出来雪碧图样式的路径
          cssImageRef: "../images/sprites.png",
        },
        spritesmithOptions: {
          // 排版方向,自上而下排版
          algorithm: "top-down",
        },
      })
    );
  },
};
  • 重写雪碧图样式

首先新建一个.scss样式文件,然后引入到入口文件中。我们将在这个新建的.scss样式文件中重写雪碧图的样式。

// 引入生成出来的雪碧图样式文件
@import "./sprites.scss";

@mixin spritesRewrite($sprites) {
  @each $sprite in $sprites {
    //   小图片的名称
    $sprite-name: nth($sprite, 10);
    // hover效果的小图片名称
    $sprite-hover-name: #{$sprite-name}_hover;

    .icon-#{$sprite-name} {
      display: inline-block;
      @include sprite($sprite);

      //   判断是否存在对应的hover小图片
      @if variable-exists($sprite-hover-name) {
        &:hover {
          @extend .icon-#{$sprite-hover-name};
        }
      }
    }
  }
}

@include spritesRewrite($spritesheet-sprites);
  • 使用

首先要先在上面配置文件中的icons目录下发小图片。比如现在目录下有文件名为star.pngstar_hover.png的 png 图片,star_hover.png图片是鼠标悬浮在star.png图片上面所需要展现出来的图片。我们只需要icon-${文件名}这样使用即可

<span class="icon-star">span>

现在,上面的 span 元素就会显示出一个对应的小图片了,并且鼠标悬浮上去还会有 hover 图片的效果了

注意:如果不存在${文件名}_hover.png格式名称的图片,鼠标悬浮上去的时候是没有 hover 效果的

全局注入 scss 变量

在项目中我们可能会使用一些 scss 变量,比如主题色,字体大小,背景色等变量,这些变量在很多地方都用到了。为了减少在文件中的引入次数,我们借助style-resources-loadervue-cli-plugin-style-resources-loader将这些 scss 变量注册为全局变量。这样就不用在使用这些 scss 变量的时候去引入 scss 变量文件,大大地提高了我们的开发效率。

  • 安装
npm i style-resources-loader vue-cli-plugin-style-resources-loader -D
  • 修改vue.config.js
const path = require("path");
module.exports = {
  pluginOptions: {
    /**
     * 全局 scss 变量配置
     * @param {string | string[]} patterns 你想要全局注册的 scss 变量
     */
    "style-resources-loader": {
      preProcessor: "scss",
      patterns: [
        path.resolve(__dirname, "src/assets/styles/variables.scss"),
        path.resolve(__dirname, "src/assets/styles/mixins/*.scss"),
      ],
    },
  },
};

注意:这里我强烈建议大家把一些主题色,标题颜色,边框颜色,字号大小等抽取出来放在一个文件中,这样可以方便后期管理。如果你想要做主题变换,这就相当的方便了。同时还有一些背景图,我建议大家也把这些背景图统一放在这里管理

打包分离公共库

我们开发项目的时候,一些库的版本号基本上是不会变的,比如vuevuexvue-routerelement-ui等等。这些库我们可以单独进行打包,然后通过webpack.DllPluginwebpack.DllReferencePluginadd-asset-html-webpack-plugin引入打包后的文件。这样做的好处是:1、提高构建打包的速度,每次打包的时候可以跳过对这些公共库的打包。2、客户端缓存,因为这些公共库的版本号基本不会变化的,所以我们不需要在打包的时候给这些文件添加 hash 值。这样用户在下载了一次公共库文件之后,后续我们在进行更新,客户端还是使用的是缓存的文件。从而提高用户的体验。

  • webpack.dll.config.js 配置

DLLPluginwebpack内置的插件,不需要安装。新建一个webpack.dll.config.js文件:

const path = require("path");
const webpack = require("webpack");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");

module.exports = {
  mode: "production",
  entry: {
    // 需要打包的公共库
    vendor: ["vue", "vuex", "vue-router", "axios"],
  },
  output: {
    // 输出的动态链接库的文件名称,[name] 代表当前动态链接库的名称
    filename: "[name].dll.js",
    path: path.resolve(__dirname, "lib/dll"),
    // library必须和后面dllplugin中的name一致
    library: "[name]_[hash]",
  },
  plugins: [
    new CleanWebpackPlugin(),
    new webpack.DllPlugin({
      // 动态链接库的全局变量名称,需要和 output.library 中保持一致
      name: "[name]_[hash]",
      // 描述动态链接库的 manifest.json 文件输出时的文件名称
      path: path.join(__dirname, "lib/dll", "[name]-manifest.json"),
    }),
  ],
};

然后我们在命令行中输入

webpack --config webpack.dll.config.js
  • 修改 vue.config.js
const webpack = require("webpack");
const AddAssetHtmlPlugin = require("add-asset-html-webpack-plugin");
const path = require("path");

module.exports = {
  configureWebpack(config) {
    const plugins = [];
    plugins.push(
      // 告诉webpack使用了哪些第三方库代码
      new webpack.DllReferencePlugin({
        // manifest.json 文件路径
        manifest: require(path.resolve(
          __dirname,
          "lib/dll/vendor-manifest.json"
        )),
      })
    );
    plugins.push(
      // 自动给html文件添加静态资源文件
      new AddAssetHtmlPlugin({
        // 公共库打包出来的文件路径
        filepath: path.resolve(__dirname, "lib/dll/vendor.dll.js"),
        // 引用路径
        publicPath: "./statics/dll",
        // 最终输出的目录
        outputPath: "./statics/dll",
      })
    );
    config.plugins = [...config.plugins, ...plugins];
  },
};

注入全局变量

有时候我们可能会根据一些环境变量去做不同的处理。这里我们有 2 中方式去注入全局变量,一种是借助webpack.DefinePlugin,另外一种是借助脚手架自带的。通常我们会往全局中注入版本号,打包时间,打包人等信息,然后把这些信息挂载到 window 上面。项目放到正式环境之后可以在浏览器控制台中输入变量名称,查看版本号,打包时间等信息,方便检查是否已经进行更新了。

  • 使用webpack.DefinePlugin

webpack.DefinePlugin 传入的全局变量是key-value形式的,其中valuekey必须是字符串。如果你想传入一个对象,就需要使用JSON.stringify将它转化为 json 字符串。

vue.config.js修改如下:

const webpack = require("webpack");
module.exports = {
  configureWebpack(config) {
    const plugins = [];
    const VERSION =
      require("./package.json").version.replace(/\./g, "") +
      getFormatDate(new Date());

    plugins.push(
      new webpack.DefinePlugin({
        PROJECT_MESSAGE: JSON.stringify({ version: VERSION, author: "xxx" }),
      })
    );
  },
};

在客户端代码中访问

console.log(PROJECT_MESSAGE);
  • 使用脚手架自带的

在项目根目录下放置下列文件来指定环境

.env                # 在所有的环境中被载入
.env.local          # 在所有的环境中被载入,但会被 git 忽略
.env.[mode]         # 只在指定的模式中被载入
.env.[mode].local   # 只在指定的模式中被载入,但会被 git 忽略

其中mode有:development、test 、production。

文件内容只能包含键=值,如下:

FOO=bar
VUE_APP_NOT_SECRET_CODE=some_value

注意:只有NODE_ENVBASE_URL和以VUE_APP_开头的变量才能被注入到客户端代码中。

上述的方式只能是一些写死的变量,对于一些动态变量,比如版本信息,我们可以在vue.config.js中计算环境变量,但是还是需要以VUE_APP_开头。

const pack = require("./package.json");
process.env.VUE_APP_MESSAGE = JSON.stringify({
  version: pack.version,
  name: "xxx",
});

module.exports = {
  // config
};

在客户端代码中访问

console.log(process.env.VUE_APP_SECRET);

删除 console.log

在开发的时候,有些开发人员喜欢使用console.log进行调试,但是到了部署到线上的时候,需要删除这些代码。原因如下:1、减少项目体积。别看console.log这个代码并不多,当项目到了一定规模的时候,还是会占用不少的体积的。2、防止信息泄露。有时候我们会打印出一些用户信息,如果不及时删除,可能会泄露了用户的信息。所以我们可以在vue.config.js中配置如下代码。

module.exports = {
  configureWebpack: (config) => {
    // 生产环境下生效
    if (process.env.NODE_ENV === "production") {
      // 配置删除 console.log
      config.optimization.minimizer[0].options.terserOptions.compress.drop_console = true;
    }
  },
};

代码分割

对于一个 vue 项目,在打包的时候,会有一个叫chunk-vendors的 js 文件,这个文件包含了一些业务代码和第三方库的代码,所以体积一般比较大,浏览器加载的时长也比较长。我们可以把这部分代码进行分割,分成多个文件。这样浏览器在加载时会并行加载,对于不变的代码,会直接从缓存中读取,提升了文件的访问速度。在vue.config.js中配置如下代码:

module.exports = {
  configureWebpack: (config) => {
    config.when(
      process.env.NODE_ENV === "production", // 配置生产环境生效
      (config) => {
        config.optimization.splitChunks({
          chunks: "all", // 将对什么类型的代码进行分割,三种值:all: 全部 | async: 异步,按需加载的代码 | initial: 入口代码块
          cacheGroups: {
            // 缓存组
            // 定义 libs 缓存组,分割从 node_modules 中引入的代码
            libs: {
              name: "chunk-libs", // 分割成的文件名
              test: /[\\/]node_modules[\\/]/, // 匹配 node_modules 中模块
              priority: 10, // 优先级,当模块同时命中多个缓存组的规则时,分配到优先级高的缓存组
              chunks: "initial", // 这里覆盖上面的 chunks: 'all',仅打包最初依赖的第三方库
            },
            // 项目使用 iview 组件开发的,定义 iviewUI 缓存组,用于分割 iview 代码
            iviewUI: {
              name: "chunk-iviewUI",
              priority: 20, // 优先级 20,命中 iview 代码时,优先分割到此组里
              test: /[\\/]node_modules[\\/]_?iview(.*)/, // 匹配 iview 代码
            },
          },
        });
      }
    );
  },
};

注意:在我们写 vue 路由的时候,通常会使用动态引入模块的方式来加载路由组件,方式如下:

import VueRouter from "vue-router";
const router = new VueRouter({
  routes: [
    {
      path: "/home",
      name: "home",
      component: () =>
        import(/* webpackChunkName: "home" */ "../views/home/index.vue"),
    },
  ],
});

我们引入模块的时候最好可以给它来一个webpackChunkName表示打包出来的文件名,相同功能或者模块的使用同一个webpackChunkName表示。因为每个文件打包出来的体积可能只有几 kb,这样子会增加请求的次数,所以我们需要把相同功能或者模块的代码取一个相同的webpackChunkName,这样子在打包的时候才能把这些代码放置到同一个文件中。反正打包出来的文件不能太大也不能太小要适中,太小会增加请求次数,太大影响加载的时长。

配置 CDN

这种方式很少见,因为考虑到 cdn 可能会崩掉(反正我没见过崩掉),或者其他原因。不过这也是一种优化项目的方案,毕竟在自家服务器带宽或者流量有限制的时候,使用 cdn 能大大的减少后台的压力。

注意:如果采用了上面 打包分离公共库 的方案,就不要使用这种方案了。

vue.config.js中配置如下代码:

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 地址:

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" />
    
    <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>

图片压缩

图片压缩会影响图片的质量的。如果对图片质量有要求的不建议使用。如果图片数量比较多,并且图片体积比较大,建议把图片进行压缩。这个需要安装image-webpack-loader

vue.config.js中配置如下代码:

module.exports = {
  chainWebpack: (config) => {
    // 配置图片压缩
    config.module
      .rule("images")
      .use("image-webpack-loader")
      .loader("image-webpack-loader")
      .options({
        bypassOnDebug: true,
      })
      .end();
  },
};

IgnorePlugin

有些依赖包,里面一些文件是没有用到的,但是打包的时候也会被一起打包进去,这个时候就可以使用IgnorePlugin来忽略这部分文件的打包。

moment 为例,忽略语言包,在vue.config.js中配置如下代码:

module.exports = {
  chainWebpack: (config) => {
    config.plugins.push(
      new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/) // 忽略打包语言包
    );
  },
};

代码规范

我们在使用 vue 脚手架创建项目的时候就已经把 eslint 的相关东西集成进来了。我们要考虑的是代码提交的时候怎么去检查代码是否符合规范,以及在提交的时候自动格式化代码,保持提交的代码统一。还有就是规范提交代码的信息。

关于这部分,我们可以借助huskylint-staged这些工具。但是要注意的是,我们这里安装的husky版本是4.x的,最新版本已经去到了6.x。但是6.x版本的似乎并不是很好用,而且用法也发生了很大改变。所以我们还是用回4.x的版本

  • 安装依赖
npm i [email protected] lint-staged commitizen @commitlint/cli @commitlint/config-conventional stylelint stylelint-config-recess-order stylelint-config-recommended-scss stylelint-config-standard stylelint-scss -D
  • 提交前检查代码,并自动格式化代码
    提交代码前先使用prettier对代码进行格式化,这样可以保证提交上去的代码格式统一。然后在使用 eslint对代码进行自动修复,最后在使用eslint检查代码是否符合规范,符合就可以提交,不符合就不能进行提交。在package.json中添加如下代码:
  "husky": {
    "hooks": {
      "pre-commit": "lint-staged"
    }
  },
  "lint-staged": {
    "*.{js,vue,ts}": [
      "prettier --write",
      "vue-cli-service lint --fix",
      "vue-cli-service lint",
      "git add"
    ]
  },

在项目根目录新建一个.prettierrc 文件,写入一下配置,用于给prettier插件进行格式化的:

{
  "singleQuote": true,
  "trailingComma": "none",
  "endOfLine": "auto"
}
  • 关于样式

vuecli 生成的项目只有eslint,并没有样式文件检查的相关东西。我们需要自行进行配置。在项目根目录新建一个.stylelintrc 文件,写入以下配置,用于给stylelint插件进行代码校验和修复的:

{
  "extends": [
    "stylelint-config-standard",
    "stylelint-config-recommended-scss",
    "stylelint-config-recess-order"
  ],
  "plugins": [
    "stylelint-scss"
  ],
  "rules": {
    "font-family-no-missing-generic-family-keyword": null,
    "indentation": 2,
    "no-descending-specificity": null,
    "selector-pseudo-class-no-unknown": null,
    "selector-pseudo-element-no-unknown": [
      true, {
        "ignorePseudoElements": ["v-deep"]
        }
      ],
    "property-no-unknown": null,
    "declaration-colon-newline-after": null
  }
}

如果你需要忽略某些样式文件,可以新建一个.stylelintignore文件,写法跟.gitignore文件一样

package.json 中添加如下代码:

  "husky": {
    "hooks": {
      "pre-commit": "lint-staged"
    }
  },
  "lint-staged": {
    "*.{html,vue,css,sass,scss}": [
      "stylelint **/*.{html,vue,css,sass,scss} --fix"
    ]
  },
  • 检查提交信息规范
    这里参考了angular的提交信息规范。在package.json中添加如下代码:
  "scripts": {
    "commit": "git cz"
  },
  "husky": {
    "hooks": {
      "commit-msg": "commitlint -e $HUSKY_GIT_PARAMS"
    }
  },
  "config": {
    "commitizen": {
      "path": "./node_modules/cz-conventional-changelog"
    }
  },

然后再项目根目录下新建一个commitlint.config.js文件,写入以下代码

module.exports = {
  extends: ["@commitlint/config-conventional"],
  rules: {
    "type-enum": [
      2,
      "always",
      [
        "build", // 主要目的是修改项目构建系统(例如 glup,webpack,rollup 的配置等)的提交
        "ci", // 主要目的是修改项目继续集成流程(例如 Travis,Jenkins,GitLab CI,Circle等)的提交
        "docs", // 文档更新
        "examples", // 开发测试
        "feat", // 新增功能
        "fix", // bug 修复
        "perf", // 性能优化
        "refactor", // 重构代码(既没有新增功能,也没有修复 bug)
        "style", // 不影响程序逻辑的代码修改(修改空白字符,补全缺失的分号等)
        "test", // 新增测试用例或是更新现有测试
        "revert", // 回滚某个更早之前的提交
        "chore", // 不属于以上类型的其他类型(日常事务)
      ],
    ],
  },
};

最后,提交代码的时候使用npm run commit即可。

关于前端代码工作流这部分的东西,可以参考我的另一篇博客,里面有更详细的说明。

关于CICD(自动部署)

这里说明一下,我们的自动部署是针对测试环境的,并不针对生产环境的,生产环境是前端把包打好发送给后台,由后台统一去更新。

说到CICD有人可能第一时间想到的就是jenkins。但是我们并不会去考虑使用jenkins,原因如下

  • jenkins比较吃硬件,占用内存也比较高,服务器资源有限。而且只是为了部署前端而去搞这个东西就显得有点鸡肋。
  • 打包速度慢。我们测试过jenkins一次打包更新大概需要5-8分钟。jenkins打包经历四部分,拉取代码–安装依赖–打包–部署。当然,慢还是跟硬件有点关系的,这就是上面所说的吃硬件。
  • 钩子函数触发频繁,导致更新频繁。jenkins打包更新是依靠webhook来触发的,一旦有提交就会触发webhook的。而我们提交代码都是比较细化,所以可能会提交很多次,从而多次触发更新。
  • 开发人员需要控制何时去更新。

所以我们的做法就会自己实现一个本地自动化部署脚本,开发人员只需要运行npm run deploy即可打包更新到测试环境。具体打包部署流程如下:拉取代码–安装依赖–打包代码–压缩成giz文件–ssh连接服务器–删除服务器上的旧文件–上传giz文件–解压giz文件–删除giz文件。这样子就很方便我们开发人员进行开发测试和更新。

关于这部分的代码,大家可以参考我的另一篇博客

另外,如果大家对jenkins有兴趣的,可以参考我的这篇博客

总结

上面的东西都是基于我平时工作开发中总结出来的。有的属于项目上的性能优化,比如雪碧图,图片压缩等等。有的属于工程化的,比如代码规范,自动化部署。相信这些东西大家在平常的开发中或多或少都会用到一些的。如果大家还有什么要补充的欢迎下方留言。

你可能感兴趣的:(vue.js,性能优化,工程化)