阅读 22113

备战2021:vite工程化实践,建议收藏

vite是个啥

vite是一个开发构建工具,开发过程中它利用浏览器native ES Module特性导入组织代码,生产中利用rollup作为打包工具,它有如下特点:

  • 光速启动
  • 热模块替换
  • 按需编译

本文目标

说白了vite就是为开发者量身定做的一套先进的开发工具,按需编译、热模块替换等特性使我们开发时免除了重新打包的等待时间,开发体验丝滑,默认还整合了vue3,是居家旅行、杀人灭口之必备良药。目前vite已经是正式版,相关的生态正在迅速繁荣起来,我也第一时间在工程化方面做了一些探索,希望能够抛砖引玉,欢迎广大掘友拍砖。

安装vite

$ npm init vite-app <project-name>
$ cd <project-name>
$ npm install
$ npm run dev
复制代码

代码组织形式分析

关键变化是index.html中的入口文件导入方式

image-20201102112021740

这样main.js中就可以使用ES6 Module方式组织代码

image-20201102112145517

浏览器会自动加载这些导入,vite会启动一个本地服务器处理不同这些加载请求,对于相对地址的导入,要根据后缀名处理文件内容并返回,对于裸模块导入要修改它的路径为相对地址并再次请求处理。

image-20201102112732700

再根据模块package.json中的入口文件选项获取要加载的文件。

image-20201102113331064

对于开发者而言,整体没有大的变化。

资源加载方式解析

直接导入CSS文件

vite中可以直接导入.css文件,样式将影响导入的页面,最终会被打包到style.css

image-20201102111816764

在我们程序中,除了全局样式大部分样式都是以形式存在于SFC中

Scoped CSS

<style scoped>
/**/
</style>
复制代码

CSS Module

SFC中使用CSS Module

<style module>
/**/
</style>
复制代码

范例:修改组件样式为CSS Module形式 image-20201102115023004

image-20201102115050888

JS中导入CSS Module:将css文件命名为*.module.css即可

image-20201103120717371

CSS预处理器

安装对应的预处理器就可以直接在vite项目中使用。

<style lang="scss">
/* use scss */
</style>
复制代码

或者在JS中导入

import './style.scss'
复制代码

PostCSS

vite自动对 *.vue 文件和导入的.css 文件应用PostCSS配置,我们只需要安装必要的插件和添加 postcss.config.js 文件即可。

module.exports = {
  plugins: [
    require('autoprefixer'),
  ]
}
复制代码
npm i postcss autoprefixer@8.1.4
复制代码

资源URL处理

引用静态资源

我们可以在*.vue 文件的template, style和纯.css文件中以相对和绝对路径方式引用静态资源。

<!-- 相对路径 -->
<img src="./assets/logo.png">
<!-- 绝对路径 -->
<img src="/src/assets/logo.png">
复制代码
<style scoped>
#app {
  background-image: url('./assets/logo.png');
}
</style>
复制代码
public目录

public 目录下可以存放未在源码中引用的资源,它们会被留下且文件名不会有哈希处理。

这些文件会被原封不动拷贝到发布目录的根目录下。

<img src="/logo.png">
复制代码

注意引用放置在 public 下的文件需要使用绝对路径,例如 public/icon.png 应该使用 /icon.png引用

代码规范:eslint

我们借助eslint规范项目代码,通过prettier做代码格式化。

首先在项目安装依赖,package.json

{
  "scripts": {
    "lint": "eslint \"src/**/*.{js,vue}\""
  },
  "devDependencies": {
    "@vue/eslint-config-prettier": "^6.0.0",
    "babel-eslint": "^10.1.0",
    "eslint": "^6.7.2",
    "eslint-plugin-prettier": "^3.1.3",
    "eslint-plugin-vue": "^7.0.0-0",
    "prettier": "^1.19.1"
  }
}
复制代码

然后配置lint规则,.eslintrc.js

module.exports = {
  root: true,
  env: {
    node: true,
  },
  extends: ["plugin:vue/vue3-essential", "eslint:recommended", "@vue/prettier"],
  parserOptions: {
    parser: "babel-eslint",
  },
  rules: {
    "no-console": process.env.NODE_ENV === "production" ? "warn" : "off",
    "no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off",
    "prettier/prettier": [
      "warn",
      {
        // singleQuote: none,
        // semi: false,
        trailingComma: "es5",
      },
    ],
  },
};
复制代码

如果有必要还可以配置prettier.config.js修改prettier的默认格式化规则

module.exports = {
  printWidth: 80, // 每行代码长度(默认80)
  tabWidth: 2, // 每个tab相当于多少个空格(默认2)
  useTabs: false, // 是否使用tab进行缩进(默认false)
  singleQuote: false, // 使用单引号(默认false)
  semi: true, // 声明结尾使用分号(默认true)
  trailingComma: 'es5', // 多行使用拖尾逗号(默认none)
  bracketSpacing: true, // 对象字面量的大括号间使用空格(默认true)
  jsxBracketSameLine: false, // 多行JSX中的>放置在最后一行的结尾,而不是另起一行(默认false)
  arrowParens: "avoid", // 只有一个参数的箭头函数的参数是否带圆括号(默认avoid)
};
复制代码

测试环境

利用jest@vue/test-utils测试组件

安装依赖

"jest": "^24.0.0",
"vue-jest": "^5.0.0-alpha.3",
"babel-jest": "^26.1.0",
"@babel/preset-env": "^7.10.4",
"@vue/test-utils": "^2.0.0-beta.9"
复制代码

配置babel.config.js

module.exports = {
  presets: [
    [
      "@babel/preset-env", { 
        targets: { 
          node: "current" 
        } 
      }
    ]
  ],
};
复制代码

配置jest.config.js

module.exports = {
  testEnvironment: "jsdom",
  transform: {
    "^.+\\.vue$": "vue-jest",
    "^.+\\js$": "babel-jest",
  },
  moduleFileExtensions: ["vue", "js", "json", "jsx", "ts", "tsx", "node"],
  testMatch: ["**/tests/**/*.spec.js", "**/__tests__/**/*.spec.js"],
  moduleNameMapper: {
    "^main(.*)$": "<rootDir>/src$1",
  },
};
复制代码

启动脚本

"test": "jest --runInBand"
复制代码

测试代码,tests/example.spec.js

import HelloWorld from "main/components/HelloWorld.vue";
import { shallowMount } from "@vue/test-utils";

describe("aaa", () => {
  test("should ", () => {
    const wrapper = shallowMount(HelloWorld, {
      props: {
        msg: "hello,vue3",
      },
    });
    expect(wrapper.text()).toMatch("hello,vue3");
  });
});
复制代码

lint配置添加jest环境,要不然会有错误提示:

module.exports = {
  env: {
    jest: true
  },
}
复制代码

linttestgit挂钩

npm i lint-staged yorkie -D
复制代码
"gitHooks": {
  "pre-commit": "lint-staged",
  "pre-push": "npm run test"
},
"lint-staged": {
  "*.{js,vue}": "eslint"
},
复制代码

正常情况下安装 yorkie 后会自动安装提交钩子 如果提交钩子未生效可以手动运行 node node_modules/yorkie/bin/install.js 来安装。 当然,你也可以运行 node node_modules/yorkie/bin/uninstall.js 来卸载提交钩子。

typescript整合

vite可直接导入.ts 文件,在SFC中通过<script lang="ts">使用

范例:使用ts创建一个组件

<script lang="ts">
import { defineComponent } from 'vue'

interface Course {
  id: number;
  name: string;
}
  
export default defineComponent({
  setup() {
    const state = ref<Course[]>([]);
    setTimeout(() => {
      state.value.push({ id: 1, name: "全栈架构师" });
    }, 1000);
  },
});
</script>
复制代码

ts版本指定,package.json

{
  "devDependencies": {
    "typescript": "^3.9.7"
  }
}
复制代码

ts参考配置,tsconfig.json

{
  "compilerOptions": {
    "target": "esnext",
    "module": "esnext",
    "moduleResolution": "node",
    "isolatedModules": true,
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "experimentalDecorators": true,
    "lib": ["dom", "esnext"]
  },
  "exclude": ["node_modules", "dist"]
}
复制代码

项目配置

项目根目录创建vite.config.js,可以对vite项目进行深度配置。

定义别名

导入的别名,避免出现大量相对路径,优雅且不易出错

src/components定义别名,vite.config.js

const path = require("path");

module.exports = {
  alias: {
    // 路径映射必须以/开头和结尾
    "/comps/": path.resolve(__dirname, "src/components"),
  },
};
复制代码

使用

import CourseAdd from "/comps/CourseAdd.vue";
import Comp from "/comps/Comp.vue";
复制代码

代理

配置服务器代理,vite.config.js

export default {
  proxy: {
    '/api': {
      target: 'http://jsonplaceholder.typicode.com',
      changeOrigin: true,
      rewrite: path => path.replace(/^\/api/, '')
    }
  }
}
复制代码

使用

fetch("/api/users")
  .then(response => response.json())
  .then(json => console.log(json));
复制代码

数据mock

安装依赖

npm i mockjs -S
npm i vite-plugin-mock cross-env -D
复制代码

引入插件,vite.config.js

plugins: [
  createMockServer({
    // close support .ts file
    supportTs: false,
  }),
],
复制代码

设置环境变量,package.json

"dev": "cross-env NODE_ENV=development vite"
复制代码

创建mock文件,mock/test.js

export default [
  {
    url: "/api/users",
    method: "get",
    response: req => {
      return {
        code: 0,
        data: [
          {
            name: "tom",
          },
          {
            name: "jerry",
          },
        ],
      };
    },
  },
  {
    url: "/api/post",
    method: "post",
    timeout: 2000,
    response: {
      code: 0,
      data: {
        name: "vben",
      },
    },
  },
];

复制代码

模式和环境变量

使用模式做多环境配置,vite serve时模式默认是developmentvite build时是production

创建配置文件.env.development

VITE_TOKEN=this is token
复制代码

代码中读取

import.meta.env.VITE_TOKEN
复制代码

打包和部署

打包

使用npm run build执行打包

部署

手动上传dist中的内容到服务器,再配置好nginx当然可以,但是这一过程最好自动化处理,避免前面这些繁琐的操作。我们这里利用github actions实现ci/cd过程。

Github Actions让我们可以在Github仓库中直接创建自定义的软件开发生命周期工作流程。

image-20201122221106501

准备工作:

阿里云linux服务器

linux操作

阿里云相关操作

第一步:配置workflow,下面的配置可以在我们push代码时自动打包我们应用并部署到阿里云服务器上,在项目根目录下创建.github/workflows/publish.yml

name: 打包应用并上传阿里云

on:
  push:
    branches:
      - master

jobs:
  build:
    # runs-on 指定job任务运行所需要的虚拟机环境(必填字段)
    runs-on: ubuntu-latest
    steps:
      # 获取源码
      - name: 迁出代码
        # 使用action库  actions/checkout获取源码
        uses: actions/checkout@master
      # 安装Node10
      
      - name: 安装node.js
        # 使用action库  actions/setup-node安装node
        uses: actions/setup-node@v1
        with:
          node-version: 14.0.0

      # 安装依赖
      - name: 安装依赖
        run: npm install

      # 打包
      - name: 打包
        run: npm run build

      # 上传阿里云
      - name: 发布到阿里云
        uses: easingthemes/ssh-deploy@v2.1.1
        env:
          # 私钥
          SSH_PRIVATE_KEY: ${{ secrets.PRIVATE_KEY }}
          # scp参数
          ARGS: "-avzr --delete"
          # 源目录
          SOURCE: "dist"
          # 服务器ip:换成你的服务器IP
          REMOTE_HOST: "47.98.252.43"
          # 用户
          REMOTE_USER: "root"
          # 目标地址
          TARGET: "/root/vue-in-action"
复制代码

第二步:在github当前项目下设置私钥选项

截屏2020-11-20 上午11.04.57

复制本地私钥,~/.ssh/id_rsa

# ssh秘钥生成过程自行百度
cd .ssh/
cat id_rsa
复制代码

截屏2020-11-20 上午11.09.37

复制并填写到github-secretes

image-20201120111150690

第三步:在阿里云服务器上配置nginx

登录服务器

ssh root@47.98.252.43 # ip换成你的
复制代码

配置nginx

cd /etc/nginx/sites-enabled/
vi vue-in-action
复制代码

添加如下配置

server {
    listen 8080;
    server_name 47.98.252.43;
    location / {
        root /root/vue-in-action/dist/;
        index index.html index.htm;
    }
}
复制代码

重启nginx:nginx -s reload

第四步:push代码,触发workflow

大功告成,激动,赶紧验证一下上传结果

image-20201122221429983

访问:47.98.252.43:8080 试试效果吧!!!

配套视频演示

我专门录了一套视频演示本文所做的所有操作,喜欢看视频学习的小伙伴移步:

「村长」vite工程化

制作不易,求一个3连关注不过分吧!?

后续计划

后续我打算把一系列项目实践内容整合进来,包括不限于如下内容:

  • 样式管理
  • 页面布局
  • 权限控制
  • 图标管理
  • 请求封装
  • 数据可视化

大家点个赞收藏一下,以便后续学习。

支持村长

关于工程化就说到这里,这篇内容折腾了很久,踩了不少坑,掉了几根头发,小伙伴们点个赞👍鼓励一下。

我近期的文章(感谢掘友的鼓励与支持🌹🌹🌹):

文章分类
前端
文章标签