The introduction

As Vue3 has been accepted by the vast number of developers and its own ecology has been gradually improved, more students are improving the engineering direction of Vue3. This article just introduces how to better use Vue3 and its peripheral plug-ins, and let them combine into the whole project.

In addition, Vue3 supports Typescript syntax programming. In order to explore the engineering construction of new technologies, this article will integrate Typescript, Vite, Pinia and other official periperians into the project.

Next, in order to make you better understand the idea of engineering this project, this paper will read the following key words step by step (skip the first 4 steps to see the project code) :

  • Vue3 script setup
  • Typescript
  • Vite
  • pinia
  • Engineering construction


script setup

< Script Setup > is a compile-time syntactic sugar for using composite apis in single-file components (SFC).

A simple demo comparing script-setup with script:

// Single-file component script-setup writing mode
<script setup>
import { ref } from 'vue'

const count = ref(0)
</script>

<template>
  <button @click="count++">{{ count }}</button>
</template>
Copy the code
// Normal script writing mode
<script>
import { ref } from 'vue'

export default {
    setup(props) {
      const count = ref(0)

      // Expose to template
      return {
        count
      }
    }
}
</script>

<template>
  <button @click="count++">{{ count }}</button>
</template>
Copy the code

As you can see from the above example, script-Setup weakens the vue template programming experience and makes the code simpler. Developers simply need to put the logic in script after introducing the correct hooks.

All components of the project use this development mode, and vue officially recognizes its advantages over normal

  • Less boilerplate content, cleaner code.
  • You can declare props and throw events using pure Typescript.
  • Better runtime performance (their templates are compiled into renderers of the same scope as them, without any intermediate proxies).
  • Better IDE type inference performance (less language server work to extract types from code)

Finally, Vue3 is a revolution of vue-hooks, which make component writing more portable and concise through compositionApi references. Script-setup makes this experience even more complete, bringing single-file component writing closer to functional programming, switching seamlessly between React and Vue.


Typescript

In recent years, TypeScript has become more and more popular on the front end, and TypeScript has become a necessary skill on the front end. TypeScript is a superset of JS types and supports generics, types, namespaces, enumerations, etc., making up for the shortcomings of JS in large-scale application development.

In vue2, if you want to use typescript, you need to borrow decorators such as vue-class-Component and vue-property-decorator, and change the code structure for vUE to recognize, which is inconvenient.

By Vue3’s time, frameworks are perfectly typescript compliant and are easy to configure and code intrusive, which is a great benefit to developers.


Vite

Vite is a new front-end build tool that dramatically improves the front-end development experience. Compared to Webpack, Vite still has its very unique advantages, here is a recommended article “Vite good and bad” for your reference.

Why did the project choose vite instead of webpack? Considering the community and individuals, there are several points :(I will not expand the details, the tweets have been very detailed analysis)

  • Vite is lighter and fast enough to build

    Webpack is implemented using NodeJS, while Viite uses esbuild pre-built dependencies. Esbuild is written using Go and is not an order of magnitude faster than pre-built dependencies of packers written in JavaScript.
  • Vue official production, Vue project compatibility is good
  • Rapid development momentum, the future can be expected

Of course, everything has two sides, so far, Vite also has many defects, such as: ecology is not mature webpack, hidden unstable factors in the production environment are the problems it is facing now.

However, if we dare to move forward with our dreams, where will the technological development come from without the birth of new forces? Vite, by contrast, is more like a teenager and has moved on.


Pinia

Pinia is a lightweight state management library for vue.js that has recently become popular. It uses the new reaction system in Vue 3 to build an intuitive and fully typed state management library.

Compared to Vuex, Pinia has the following advantages:

  • Full TypeScript support: Adding TypeScript is easier than adding TypeScript to Vuex
  • Extremely lightweight (about 1KB)
  • Store actions are scheduled as regular function calls, rather than using the Dispatch method or MapAction helper functions, which is common in Vuex
  • Support for multiple stores
  • Support Vue DevTools, SSR and Webpack code splitting


Engineering construction

So let’s get back to the point. We use these techniques to integrate them into a project. Generally used for enterprise production projects, to have the following capabilities:

  • Strong fault tolerance and expansibility
  • High cohesion of components, reduce coupling between modules
  • Clear project execution bus, easy to add slot logic
  • Highly abstract global methods
  • Resource compression + performance optimization

Against these indicators, let’s build a preliminary engineering framework step by step.

Note: vue3 syntax, Pinia usage and other programming knowledge will not be described in detail here, you can search online or directly in the project.

1. The technology stack

Programming: Vue3. X + Typescript build tools: Vite routing | state management: vue router + Pinia UI Element: nutui

2. Engineering structure

.├ ── ├.md ├─ Index.html ├─ Mock ├─ Package.json ├─ public ├─ SRC │ ├─ app.vue Main Application │ ├─ API │ ├─ Assets Index (Graphics, LESS, CSS, Etc.) │ ├─ Components │ ├─ Constants │ ├─ env.d.ts Global Declaration │ ├─ main │ ├── ├─ ├─ ├─ ├─ ├─ ├─ Test ├─ tsconfig.json ├─.eslintrc.js eslint ├─.prettierrc.json prettier ├─.gitignore git Ignore config ├─ vt.configCopy the code

SRC /utils contains global methods that can be invoked by project-wide files, as well as the project-initialized event bus “described below”. SRC /types and SRC /constants hold the type definitions and constants of the project, respectively, and are classified by page structure.

3. Engineering configuration

Set up Vite + Vue project

# npm 6.x
npm init vite@latest my-vue-app --template vue

# NPM 7+ requires additional double lines:
npm init vite@latest my-vue-app -- --template vue

# yarn
yarn create vite my-vue-app --template vue

# pnpm
pnpm create vite my-vue-app -- --template vue
Copy the code

Then follow the prompts to operate!

Vite configuration

import { defineConfig, ConfigEnv } from 'vite';
import vue from '@vitejs/plugin-vue';
import styleImport from 'vite-plugin-style-import';

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

const path = require('path')

// https://vitejs.dev/config/
export default defineConfig(({ command }: ConfigEnv) = > {
  return {
    base: '/'.plugins: [
      vue(),
      // mock
      viteMockServe({
        mockPath: 'mock'.// Mock file address
        localEnabled:!!!!! process.env.USE_MOCK,// Develop the package switch
        prodEnabled:!!!!! process.env.USE_CHUNK_MOCK,// Make packing switch
        logger: false.// Whether to display request logs on the console
        supportTs: true
      }),
      styleImport({
        libs: [
          // Nutui loads configuration on demand, see https://nutui.jd.com/#/start
          {
            libraryName: '@nutui/nutui'.libraryNameChangeCase: 'pascalCase'.resolveStyle: name= > {
              return `@nutui/nutui/dist/packages/${name}/index.scss`; }}]})],resolve: {
      alias: [{find: The '@'.replacement: '/src'}},css: {
      // CSS preprocessor
      preprocessorOptions: {
        scss: {
          // Configure the nutui global SCSS variable
          additionalData: `@import "@nutui/nutui/dist/styles/variables.scss"; `
        },
        less: {
          charset: false.additionalData: '@import "./src/assets/less/common.less"; '}}},build: {
      terserOptions: {
        compress: {
          drop_console: true}},outDir: 'dist'.// Specify the output path
      assetsDir: 'assets' // Specify the path to generate static resources}}; });Copy the code

The mock interface is added in the mock directory. The mock interface starts with the command NPM run dev:mock.

FYI: The Vite-plugin-mock plugin provides DevTools network interception capability under vite scaffolding. If you want to implement more mock scenarios, use MockJS “project installed, ready to use”.

Coding standards

tsconfig

eslint

prettier

Event bus

The initialize(app) method is called from the main.ts entry. The initialize code is as follows:

/** * Project initializes the bus */

// Initialize the nutui style
import '@nutui/nutui/dist/style.css';

import { initRem } from '@/utils/calcRem';
import nutUiList from '@/utils/nutuiImport';
import router from '@/router';
import { createPinia } from 'pinia';
import { registerStore } from '@/store';

export const initialize = async (app: any) = > {// Initialize rem
  initRem(window.document.documentElement);
  window.calcRem(1080);
  console.trace('REM initialization is complete... ');

  // Load nutui components on demand
  Object.values(nutUiList).forEach(co= > {
    app.use(co);
  });
  console.trace('Nutui component loading complete... ');

  // Mount the route
  app.use(router);
  console.trace('Router is mounted... ');

  // Register pinia status management library
  app.use(createPinia());
  registerStore();
  console.trace('Pinia status library registered... ');
};
Copy the code

In the method, the rem adaptive layout initialization of the page, UI component loading on demand, routing, state library initialization and other operations are completed respectively. In addition, Initialize supports asynchronous logic injection, which can be added by itself and returned with the Promise package.

Ps: Initialize method execution time Before the main App is mounted, do not place DOM operation logic here

4. Request Center

SRC/API contains asynchronous requests for each page, and directories are also divided by page structure. SRC/API /index.ts is its entry file, which is used to aggregate each request module, with the following code:

import { Request } from './request';
import box from './box';
import user from './user';

// Initialize axios
Request.init();

export default {
  box,
  user
  / /... Other request modules
};
Copy the code

SRC/API /request.ts SRC/API /request.ts SRC/API /request.ts

import axios, { AxiosInstance, AxiosError, AxiosRequestConfig } from 'axios';
import {
  IRequestParams,
  IRequestResponse,
  TBackData
} from '@/types/global/request';
import { Toast } from '@nutui/nutui';

interface MyAxiosInstance extends AxiosInstance {
  (config: AxiosRequestConfig): Promise<any>;
  (url: string, config? : AxiosRequestConfig):Promise<any>;
}

export class Request {
  public static axiosInstance: MyAxiosInstance;

  public static init() {
    // Create an axios instance
    this.axiosInstance = axios.create({
      baseURL: '/api'.timeout: 10000
    });
    // Initialize the interceptor
    this.initInterceptors();
  }

  // Initialize the interceptor
  public static initInterceptors() {
    // Set the POST header
    this.axiosInstance.defaults.headers.post['Content-Type'] =
      'application/x-www-form-urlencoded';
    /** * Request interceptor * Each request is preceded by a token */ in the request header if it exists
    this.axiosInstance.interceptors.request.use(
      (config: IRequestParams) = > {
        const token = localStorage.getItem('ACCESS_TOKEN');
        if (token) {
          config.headers.Authorization = 'Bearer ' + token;
        }
        return config;
      },
      (error: any) = >{ Toast.fail(error); });// Response interceptor
    this.axiosInstance.interceptors.response.use(
      // The request succeeded
      (response: IRequestResponse): TBackData= > {
        const {
          data: { code, message, data }
        } = response;
        if(response.status ! = =200|| code ! = =0) {
          Request.errorHandle(response, message);
        }
        return data;
      },
      // The request failed
      (error: AxiosError): Promise<any> = > {const { response } = error;
        if (response) {
          // The request has been issued, but it is outside the scope of 2xx
          Request.errorHandle(response);
        } else {
          Toast.fail('Network connection is abnormal, please try again later! ');
        }
        return Promise.reject(response?.data);
      }
    );
  }

  /** * HTTP handshake error *@param The RES response callback performs different operations depending on the response *@param message* /
  private static errorHandle(res: IRequestResponse, message? :string) {
    // Determine the status code
    switch (res.status) {
      case 401:
        break;
      case 403:
        break;
      case 404:
        Toast.fail('Requested resource does not exist');
        break;
      default:
        // Error message judgmentmessage && Toast.fail(message); }}}Copy the code

There are several things going on here:

  1. Configure the AXIos instance in the interceptor Settings request and the corresponding interception operation, to order the server to returnretcodeandmessage;
  2. rewriteAxiosInstanceThe TS type (byAxiosPromisePromise<any>), correct the call to determine the type of returned data;
  3. Set an initialization functioninit(), generates an instance of AXIos for the project to call;
  4. configurationerrorHandleHandle, handling error;

Of course, in step 2, you can add additional request interception, such as RSA encryption, local cache policy, etc. When logic is too much, it is recommended to introduce through functions.

At this point, we can happily use Axios to request data.

// API module → Request center
import { Request } from './request'; userInfo: (options? : IRequestParams):Promise<TUser> =>
  Request.axiosInstance({
    url: '/userInfo'.method: 'post'.desc: 'Get user information'.isJSON: true. options })// Business module → API module
import request from '@/api/index';

request.user
  .userInfo({
    data: {
      token
    }
  })
  .then(res= > {
    // do something...
  });
Copy the code

5. SSR

To complement…


The performance test

Development environment startup

As you can see, Vite pre-bundling 6 dependencies at cold start and then adding them to the main application took only 738ms to start up.

In addition, this project also uses the viet-plugin-style-import plug-in to introduce the style of Nutui view framework as needed, which also plays a positive role in saving resources.

The built resource bundle

The subcontracting strategy is to cut according to the routing page and separate JS and CSS separately.

Lighthouse test

The above is a local test. The first screen is about 1000ms~1500ms. The pressure mainly comes from the loading of Vendor. js and the pulling of the first screen image resources (the first screen image resources come from the network). In fact, after loading through module segmentation, the js package of the home page is compressed to 4.3 KB by gzip. Of course, the real scenario is that the loading speed of local resources cannot be achieved after the cloud server is deployed in the project, but the optimization can be accelerated through CDN, and the effect is quite significant.

Performance







Refer to the article

“Composite apis” “Vite’s Good and Bad” “Core Differences between Vite and Webpack”


Write in the last

Thank you for reading and welcome to correct mistakes. GitHub project portal