The author: Flower flower poplar village head

2021 what’s

2021 is the first and even bigger Vite2, new plug-in architecture, silky development experience, and the perfect combination of Vue3. In the first round of 2021, the village head will start the front-end learning journey with Vite2+Vue3 as the theme.

In this paper, the target

Common task practices in vuE3 + VitE2 projects:

  • Infrastructure: configuration, Lint, testing, style organization, service encapsulation, data mock, UI library integration, routing, state management, etc
  • Common business writing and code organization
  • Packaging releases

Create the Vite2 project

Gossip needless to say, below we table a table of heroes Vite2

Use NPM:

$ npm init @vitejs/app
Copy the code

Specify the project name and template as prompted, or directly

$ npm init @vitejs/app my-vue-app --template vue
Copy the code

Vite2 major changes

  • Configuration options change:Vue-specific options, Create options, CSS options, JSX options, etc., alias behavior changes: no longer requires a/beginning or end
  • Vue support: via @vitejs/plugin- Vue plugin support
  • The React to support
  • HMR API changes
  • List format changes
  • Plug-in API redesign

Vue support

Vue integration is also implemented through plug-ins, just like any other framework:

SFC definitions use setup script by default.

The alias definition

No longer need to add/before and after aliases like vite1, this and Webpack project configuration can be consistent for portability.

import path from 'path'

export default {
  alias: {
    "@": path.resolve(__dirname, "src"),
    "comps": path.resolve(__dirname, "src/components"),}}Copy the code

Try it in app.vue

<script setup>
import HelloWorld from 'comps/HelloWorld.vue'
</script>
Copy the code

Plug-in API redesign

The main change in Vite2 is the plugin architecture, which is more standardized and extensible. The Vite2 plugin API extends from the Rollup plugin architecture, so it is compatible with existing Rollup plugins. The Vite plugin can also be developed and created at the same time.

I will have a separate panel discussion on plug-in writing, welcome to follow me.

Vue3 Jsx support

Vue3 JSX support requires the introduction of plugins: @vitejs/ plugin-vuE-jsx

$ npm i @vitejs/plugin-vue-jsx -D
Copy the code

Register the plugin, vite.config.js

import vueJsx from "@vitejs/plugin-vue-jsx";

export default {
  plugins: [vue(), vueJsx()],
}
Copy the code

Usage also has requirements, revamp app.vue

<! -- 1. mark JSX --> <script setup lang=" JSX "> import {defineComponent} from "vue"; import HelloWorld from "comps/HelloWorld.vue"; import logo from "./assets/logo.png" // 2. Define components with defineComponent and export Default defineComponent({render: () => ( <> <img alt="Vue logo" src={logo} /> <HelloWorld msg="Hello Vue 3 + Vite" /> </> ), }); </script>Copy the code
Mock plug-in application

Vite2 has been refactored from vite-plugin-Mock.

Installing a plug-in

npm i mockjs -S
Copy the code
npm i vite-plugin-mock cross-env -D
Copy the code

The configuration, vite. Config. Js

import { viteMockServe } from 'vite-plugin-mock'

export default {
  plugins: [ viteMockServe({ supportTs: false}})]Copy the code

Set the environment variable, package.json

{
  "scripts": {
    "dev": "cross-env NODE_ENV=development vite"."build": "vite build"}},Copy the code

engineering

For basic configuration, styling, Lint, testing, packaging and publishing, see my previous article: Vite Engineering practices

Project Infrastructure

routing

Install the vue – the router 4. X

npm i vue-router@next -S
Copy the code

Route configuration, router/index.js

import { createRouter, createWebHashHistory } from 'vue-router';

const router = createRouter({
  history: createWebHashHistory(),
  routes: [{path: '/'.component: () = > import('views/home.vue')}});export default router
Copy the code

Introduction, main. Js

import router from "@/router";
createApp(App).use(router).mount("#app");
Copy the code

Don’t forget to create home.vue and modify app.vue

Route usage changes slightly in the village chief’s video tutorial

State management

Install vuex 4. X

npm i vuex@next -S
Copy the code

Store configuration, Store /index.js

import {createStore} from 'vuex';

export default createStore({
  state: {
    couter: 0}});Copy the code

Introduction, main. Js

import store from "@/store";
createApp(App).use(store).mount("#app");
Copy the code

Usage and before basic same, village chief video tutorial

Style organization

Install the sass

npm i sass -D
Copy the code

The styles directory holds various styles

Index.scss organizes these styles as an exit, while writing some global styles

Finally import in main.js

import "styles/index.scss";
Copy the code

Note that the styles alias is added in viet.config. js

UI library

Use our own element3 from the flower hill team.

Chinese document

The installation

npm i element3 -S
Copy the code

Full introduction, main.js

import element3 from "element3";
import "element3/lib/theme-chalk/index.css";

createApp(App).use(element3)
Copy the code

Import main.js on demand

import "element3/lib/theme-chalk/button.css";
import { ElButton } from "element3"
createApp(App).use(ElButton)
Copy the code

Better to extract as plugins, plugins/element3.js

// complete introduction
import element3 from "element3";
import "element3/lib/theme-chalk/index.css";

// Import on demand
// import { ElButton } from "element3";
// import "element3/lib/theme-chalk/button.css";

export default function (app) {
  // complete introduction
  app.use(element3)

  // Import on demand
  // app.use(ElButton);
}
Copy the code

test

<el-button>my button</el-button>
Copy the code

Basic layout

We need a basic layout page for our application, similar to the following, and each page will be parent page:

Layout page, Layout /index.vue

<template> <div class="app-wrapper"> <! - the sidebar - > < div class = "sidebar - container" > < / div > <! Content container --> <div class="main-container"> <! -- Top navigation bar --> <navbar /> <! < AppMain /> </div> </div> </template> <script setup> import AppMain from "./ Components/appmain.vue "; import Navbar from "./components/Navbar.vue"; </script> <style lang="scss" scoped> @import ".. /styles/mixin.scss"; .app-wrapper { @include clearfix; position: relative; height: 100%; width: 100%; } </style>Copy the code

Don’t forget to create appmain. vue and navbar. vue

Route configuration, router/index.js

{
  path: "/".component: Layout,
  children: [{path: "".component: () = > import('views/home.vue'),
      name: "Home".meta: { title: "Home page".icon: "el-icon-s-home"}},],},Copy the code

Dynamic navigation

The side navigation

Dynamically generate side navigation menus from routing tables.

First create a Sidebar component. The configuration in the recursive output routes is a multi-level menu, Layout /Sidebar/index.vue

<template>
  <el-scrollbar wrap-class="scrollbar-wrapper">
    <el-menu
      :default-active="activeMenu"
      :background-color="variables.menuBg"
      :text-color="variables.menuText"
      :unique-opened="false"
      :active-text-color="variables.menuActiveText"
      mode="vertical"
    >
      <sidebar-item
        v-for="route in routes"
        :key="route.path"
        :item="route"
        :base-path="route.path"
      />
    </el-menu>
  </el-scrollbar>
</template>

<script setup>
import SidebarItem from "./SidebarItem.vue";
import { computed } from "vue";
import { useRoute } from "vue-router";
import { routes } from "@/router";
import variables from "styles/variables.module.scss";

const activeMenu = computed(() => {
  const route = useRoute();
  const { meta, path } = route;
  if (meta.activeMenu) {
    return meta.activeMenu;
  }
  return path;
});
</script>

Copy the code

Note: The sass file export variable resolution requires CSS Module, so the variables file should have a module infix.

Add related styles:

  • styles/variables.module.scss
  • styles/sidebar.scss
  • styles/index.scssThe introduction of

Create sideBarItem. vue component to resolve whether the current route is a navigation link or a parent menu:

Bread crumbs

Breadcrumbs can be dynamically generated by routing the match array.

Bread crumbs components, layouts/components/Breadcrumb. Vue

<template> <el-breadcrumb class="app-breadcrumb" separator="/"> <el-breadcrumb-item v-for="(item, index) in levelList" :key="item.path"> <span v-if="item.redirect === 'noRedirect' || index == levelList.length - 1" class="no-redirect" >{{ item.meta.title }}</span> <a v-else @click.prevent="handleLink(item)">{{ item.meta.title }}</a> </el-breadcrumb-item> </el-breadcrumb> </template> <script setup> import { compile } from "path-to-regexp"; import { reactive, ref, watch } from "vue"; import { useRoute, useRouter } from "vue-router"; const levelList = ref(null); const router = useRouter(); const route = useRoute(); const getBreadcrumb = () => { let matched = route.matched.filter((item) => item.meta && item.meta.title); const first = matched[0]; if (first.path ! = = = "/") {matched [{path: "/ home", meta: {title: "home page"}}]. Concat (matched); } levelList.value = matched.filter( (item) => item.meta && item.meta.title && item.meta.breadcrumb ! == false ); } const pathCompile = (path) => { var toPath = compile(path); return toPath(route.params); } const handleLink = (item) => { const { redirect, path } = item; if (redirect) { router.push(redirect); return; } router.push(pathCompile(path)); } getBreadcrumb(); watch(route, getBreadcrumb) </script> <style lang="scss" scoped> .app-breadcrumb.el-breadcrumb { display: inline-block; font-size: 14px; line-height: 50px; margin-left: 8px; .no-redirect { color: #97a8be; cursor: text; } } </style>Copy the code

Don’t forget to add dependencies: path-to-regexp

Note: Vue-Router4 no longer uses path-to-regexp to resolve dynamic paths, so this needs to be improved.

Data encapsulation

Unified encapsulation of data request services helps to solve the following problems:

  • Unified configuration request
  • Requests and responses are processed in a unified manner

Preparations:

  • Install axios:

    npm i axios -S
    Copy the code
  • Add the configuration file:.env.development

    VITE_BASE_API=/api
    Copy the code

Request encapsulation, utils/request.js

import axios from "axios";
import { Message, Msgbox } from "element3";

// Create an axios instance
const service = axios.create({
  // Prefix the request address with baseURL
  baseURL: import.meta.env.VITE_BASE_API,
  // Cookies are carried when sending a cross-domain request
  // withCredentials: true,
  timeout: 5000});// Request interception
service.interceptors.request.use(
  (config) = > {
    // Emulated the specified request token
    config.headers["X-Token"] = "my token";
    return config;
  },
  (error) = > {
    // Unified handling of request errors
    console.log(error); // for debug
    return Promise.reject(error); });// Response interceptor
service.interceptors.response.use(
  /** * The response can be processed by the status code, which can be modified according to the situation
  (response) = > {
    const res = response.data;

    // If the status code is not 20000, an error is considered
    if(res.code ! = =20000) {
      Message.error({
        message: res.message || "Error".duration: 5 * 1000});// 50008: invalid token; 50012: Other clients are logged in; 50014: Token expired;
      if (res.code === 50008 || res.code === 50012 || res.code === 50014) {
        // Log in again
        Msgbox.confirm("You have logged out. Please log in again."."Confirm", {
          confirmButtonText: "Log in again".cancelButtonText: "Cancel".type: "warning",
        }).then(() = > {
          store.dispatch("user/resetToken").then(() = > {
            location.reload();
          });
        });
      }
      return Promise.reject(new Error(res.message || "Error"));
    } else {
      returnres; }},(error) = > {
    console.log("err" + error); // for debug
    Message({
      message: error.message,
      type: "error".duration: 5 * 1000});return Promise.reject(error); });export default service;

Copy the code

Common Business Processes

Structured data presentation

El-table was used to display structured data, and el-Pagination was used for data pagination.

The file structure is as follows: list.vue displays the list, edit.vue and create.vue edit or create, internally reuse detail.vue processing, model is responsible for data business processing.

Data display in list.vue

<el-table v-loading="loading" :data="list"> <el-table-column label="ID" prop="id"></el-table-column> <el-table-column </el-table-column> <el-table-column label=" age" prop="age"></el-table-column> </el-table>Copy the code

The acquisition logic of list and loading data can be extracted from userModel.js using compsition-API

export function useList() {
  // List data
  const state = reactive({
    loading: true.// Load status
    list: [].// List data
  });

  // Get the list
  function getList() {
    state.loading = true;
    return request({
      url: "/getUsers".method: "get",
    }).then(({ data, total }) = > {
      // Set the list data
      state.list = data;
    }).finally(() = > {
      state.loading = false;
    });
  }
  
  // Obtain data for the first time
  getList();

  return { state, getList };
}
Copy the code

Used in the list. The vue

import { useList } from "./model/userModel";
Copy the code
const { state, getList } = useList();
Copy the code

Paging, list.vue

<pagination
      :total="total"
      v-model:page="listQuery.page"
      v-model:limit="listQuery.limit"
      @pagination="getList"
    ></pagination>
Copy the code

The data is also processed in the userModel

const state = reactive({
  total: 0./ / the total number of article
  listQuery: {// paging query parameters
    page: 1.// Current page number
    limit: 5.// Number of entries per page}});Copy the code
request({
  url: "/getUsers".method: "get".params: state.listQuery, // Add paging parameters to the query
})
Copy the code

Form processing

User data is added and edited using El-Form

This can be handled by a component detail.vue, the only difference being whether information is retrieved and backfilled into the form at initialization.

<el-form ref="form" :model="model" :rules="rules">
  <el-form-item prop="name" label="Username">
    <el-input v-model="model.name"></el-input>
  </el-form-item>
  <el-form-item prop="age" label="User age">
    <el-input v-model.number="model.age"></el-input>
  </el-form-item>
  <el-form-item>
    <el-button @click="submitForm" type="primary">submit</el-button>
  </el-form-item>
</el-form>
Copy the code

Data processing can also be extracted into userModel for processing.

export function useItem(isEdit, id) {
  const model = ref(Object.assign({}, defaultData));

  // During initialization, isEdit determines whether the details need to be retrieved
  onMounted(() = > {
    if (isEdit && id) {
      // Get details
      request({
        url: "/getUser".method: "get".params: { id },
      }).then(({ data }) = >{ model.value = data; }); }});return { model };
}
Copy the code

Supporting video demonstration

Best practices for Vite2 + Vue3 project “Prepare for 2021”

Form a complete set of source code

This article related source point here

Subsequent updates

There are still some questions not covered due to time, you can pay attention to the flower and Fruit Mountain team, we will continue to update.