vite + vue3 + setup + pinia + ts project practice

Posted by Ionisis on Thu, 03 Mar 2022 16:12:00 +0100

introduce

A project developed by using vite + vue3 + pinia + ant design Vue + typescript complete technical route, with the launch of second level development update, the new vue3 composition api combined with setup to enjoy the silky development experience, the new pinia state manager and excellent design experience (1k size), the barrier free transition of antd to UI component library Ant Design Vue Safe and efficient typescript type support, code specification verification and multi-level permission management~

preface

I received a request two days ago, that is to extract the permission system of the main function modules and user modules of the original project to make a new background project, and iteratively add some new functions. It seems that there is nothing

Get the source code and look at the project. Good guy, the original project is a micro application project. The main application user module is react technology stack and the sub application module is vue2 technology stack. This direct CV method seems to be out of order??, After all, what I need to do is a single page application. Just determine a technical route. Look at the code logic and run

After running and trying, the two projects are basically started in about one minute. Look at the code vue project, and the whole business logic code is written in one piece

When I thought of asking the boss for the source code, I said it was an old project. It should be faster to build a new one

There's nothing wrong with that. I don't say much. I'll open it directly. This time, I'll go directly to vite + vue3

characteristic

  • Scaffolding tools: efficient and fast Vite
  • ?? Front end frame: the most fashionable Vue3 at present
  • ?? State Manager: vue3 rookie Pinia, like react zustand, friendly api and asynchronous processing
  • ?? Development language: politically correct TypeScript
  • ?? UI components: Ant Design Vue is used by antd developers without obstacles, with familiar formula and familiar taste
  • ?? css Style: less, postcss
  • ?? Code specification: Eslint, Prettier, Commitlint
  • ?? Permission management: page level, menu level, button level and interface level
  • Dependency on demand loading: unplugin auto import, which can automatically import the used vue, vue router and other dependencies
  • ?? Component on-demand import: unplugin Vue components. Whether it is a third-party UI component or a custom component, it can realize automatic on-demand import and TS syntax prompt

Project directory

├── .husky                              // husky git hooks configuration directory
    ├── _                               // Directory file generated by husky script
    ├── commit-msg                      // The commit MSG hook is used to verify the message format
    ├── pre-commit                      // The pre commit hook is mainly used to cooperate with eslint
├── config                              // Global profile
    ├── vite                            // vite related configuration
    ├── constant.ts                     // Project configuration
    ├── themeConfig.ts                  // Theme configuration
├── dist                                // Default build output directory
├── mock                                // Front end data mock
├── public                              // Static directory under vite project
└── src                                 // Source directory
    ├── api                             // Interface related
    ├── assets                          // Public files (such as image, css, font, etc.)
    ├── components                      // Project components
    ├── directives                      // Custom instruction
    ├── enums                           // Custom constant (enumeration)
    ├── hooks                           // Customize hooks
    ├── layout                          // Global layout
    ├── router                          // route
    ├── store                           // Status manager
    ├── utils                           // Tool library
    ├── views                           // Page module directory
        ├── login                       // login page module
        ├── ...
    ├── App.vue                         // vue top level file
    ├── auto-imports.d.ts               // Unplugin auto import plug-in generation
    ├── components.d.d.ts               // Unplugin Vue components plug-in generation
    ├── main.ts                         // Project entry file
    ├── shimes-vue.d.ts                 // vite default ts type file
    ├── types                           // Project type definition folder
├── .editorconfig                       // IDE format specification
├── .env                                // environment variable
├── .eslintignore                       // eslint ignore
├── .eslintrc                           // eslint configuration file
├── .gitignore                          // git ignore
├── .npmrc                              // npm profile
├── .prettierignore                     // prettierc ignored
├── .prettierrc                         // prettierc configuration file
├── index.html                          // Entry file
├── LICENSE.md                          // LICENSE
├── package.json                        // package
├── pnpm-lock.yaml                      // pnpm-lock
├── postcss.config.js                   // postcss
├── README.md                           // README
├── tsconfig.json                       // typescript configuration file
└── vite.config.ts                      // vite

development

Project initialization

If you use the vscode editor to develop vue3, be sure to install the Volar plug-in. It is better to use it with vue3 (incompatible with the original Vetur)

use vite cli Quickly create a project

yarn create vite project-name --template vue-ts

Installation related dependencies

Recommend new generation of performance and PN management tools_ Modules dependency management is excellent

Cooperation is recommended npmrc configuration usage

# Upgrade some dependent packages to node_modules
# Solve the problem that some package modules are not found
# For pnpm
shamefully-hoist = true

# Node sass download problem
# sass_binary_site="https://npm.taobao.org/mirrors/node-sass/"

Code specification

Tools: husky, eslint, prettier

There are many specific ways to use it online. I also said it in another article before. I won't repeat it here~

a Vite2 + Typescript + React + Antd + Less + Eslint + Prettier + Precommit template

It is mainly the concept of automation to complete the specified things at an appropriate time

  • Combined with VsCode editor (format is automatically executed when saving: editor.formatOnSave: true)
  • Cooperate with Git hooks hook (execute before commit or commit: pre commit = > NPM run lint: Lint staged)

be careful:

commitlint is installed differently for different systems commitlint , the installation error may be invalid~

# Install commitlint cli and conventional config
npm install --save-dev @commitlint/{config-conventional,cli}
# For Windows:
npm install --save-dev @commitlint/config-conventional @commitlint/cli

function

vue capability support

Template syntax with jsx syntax is very convenient and flexible to use~

Some required plug-ins

{
    // "@ vitejs / plugin legacy": "^ 1.6.2"
    "@vitejs/plugin-vue": "^1.9.3", // vue support
    "@vitejs/plugin-vue-jsx": "^1.2.0", // jsx support
}

Status manager Pinia

vue is a new generation of state manager. Students who have used react zustand should feel familiar with it

Pinia is a wrapper around the Vue 3 Composition API. Therefore, you don't have to initialize it as a plug-in unless you need Vue devtools support, SSR support and webpack code segmentation

  • Very lightweight, only 1 KB

  • Intuitive API, intuitive and easy to learn

  • Modular design, easy to split state

  • Comprehensive TS support

    //... introduce related dependencies

    interface IUserInfoProps{
    name: string;
    avatar: string;
    mobile: number;
    auths: string[]
    }

    interface UserState {
    userInfo: Nullable;
    }

    //Create store
    export const useUserStore = defineStore({
    ID: 'app user', / / unique ID, which can be used with Vue devtools
    state: (): UserState => ({
    // userInfo
    userInfo: null,
    }),
    getters: {
    getUserInfo(): Nullable {
    return this.userInfo || null;
    },
    },
    actions: {
    setUserInfo(info: Nullable) {
    this.userInfo = info ?? null;
    },
    resetState() {
    this.userInfo = null;
    },

    /**
     * @description: fetchUserInfo
     */
    async fetchUserInfo(params: ReqParams) {
      const res = await fetchApi.userInfo(params);
      if (res) {
        this.setUserInfo(res);
      }
    },
    

    },
    })

Used in components

// TS type inference and asynchronous functions are very convenient to use
import { useHomeStore } from '/@/store/modules/home';

const store = useHomeStore();
const userInfo = computed(() => store.getUserInfo);

onMounted(async () => {
  await store.fetchInfo(); // Asynchronous function
  // ...
});

UI components are loaded on demand and imported automatically

Understand the basic concept: vite comes with on-demand loading (for js). Here we mainly do on-demand loading for styles

Scheme 1: vite plugin style import

import styleImport from 'vite-plugin-style-import'

// 
plugins:[
  styleImport({
    libs: [
      {
        libraryName: 'ant-design-vue',
        esModule: true,
        resolveStyle: (name) => {
          return `ant-design-vue/es/${name}/style/index`
        },
      }
    ]
  })
]

Scheme 2: unplugin Vue components

Recommended use unplugin-vue-components plug-in unit

The plug-in only needs to add the corresponding AntDesignVueResolver in the vite plugin. It also supports the automatic registration of customized components, which is very convenient

import { AntDesignVueResolver } from 'unplugin-vue-components/resolvers';
import Components from 'unplugin-vue-components/vite';

// vite. config. Add the following configuration to TS plugins
export default defineConfig({
  plugins: [
    Components({
      resolvers: [
        AntDesignVueResolver(), // ant-design-vue
        // ElementPlusResolver(), // Element Plus
        // VantResolver(), // Vant
      ]
    })
  ]
})

Of course, it doesn't matter if you don't have the Resolver loader of the corresponding UI framework. It also supports custom configuration

Components({
  resolvers: [
    // example of importing Vant
    (name) => {
      // where `name` is always CapitalCase
      if (name.startsWith('Van'))
        return { importName: name.slice(3), path: 'vant' }
    }
  ]
})

Another powerful function: the plug-in supports not only on-demand import of UI framework components, but also automatic on-demand import of project components

For example, when we use the Card component of Ant Design Vue or other components defined by ourselves, we don't need to import them. We can use them directly. The plug-in will automatically import them for us on demand. Combined with the TS syntax prompt, we can improve the development efficiency~

The configuration is as follows:

Components({
  // allow auto load markdown components under `./src/components/`
  extensions: ['vue'],

  // allow auto import and register components
  include: [/.vue$/, /.vue?vue/],

  dts: 'src/components.d.ts',
})

You need to add components. In the src directory d. TS file, which will be automatically updated by the plug-in

  • components.d.ts action

The direct function is to generate correspondence under the project d.tstype type file is used for syntax prompt and type detection

  • be careful

"unplugin-vue-components": "^0.17.2"

Known issues with current version: issues 174

For the notification / message component of Ant Design Vue, when used in js, the plug-in will not perform automatic import capability (styles will not be imported)

The final effect is: message Success ('xx ') can create DOM elements, but there is no relevant style code

Because the design principle of the plug-in is processed according to the use of components in vue template template, the plug-in cannot be queried during function call

Solution:

  • Use the vite plugin style import plug-in instead
  • Manually introduce the message component style globally, import 'ant design Vue / ES / message / style'
  • Manually add < a-message / > in the template of vue component for plug-in index dependency

Dependency auto import on demand

  • unplugin-auto-import

vue related defineComponent, computed, watch and other modules depend on the use, and the plug-ins are imported automatically. You don't need to care about import, you can use it directly

The plug-in supports:

  • vue
  • vue-router
  • vue-i18n
  • @vueuse/head
  • @vueuse/core
  • ...

Of course, you can also customize the configuration unplugin-auto-import

The usage is as follows:

import AutoImport from 'unplugin-auto-import/vite'

export default defineConfig({
  // ...
  plugins: [
    AutoImport({
      imports: [
        'vue',
        'vue-router',
        'vue-i18n',
        '@vueuse/head',
        '@vueuse/core',
      ],
      dts: 'src/auto-imports.d.ts',
    })
  ]
})

You need to add auto imports d. TS file, which will be automatically updated by the plug-in

The final effect is:

For example, the ref method can be used directly with corresponding TS syntax prompt, instead of manually importing {ref} from 'Vue'

Custom theme

For custom theme settings, refer to the official document configuration. There are two general methods

  1. Load on demand and modify variables with webpack/vite loader attribute
  2. Full introduction, with variables Less custom style overrides frame theme style

Here, we use the first method to load food on demand through loader configuration

Under vite project, please manually install less, pnpm add less -D

css: {
  preprocessorOptions: {
    less: {
      modifyVars: { 'primary-color': 'red' },
      javascriptEnabled: true, // This is necessary
    },
  },
}

Note: after using the plugin Vue components for on-demand loading configuration, the settings of relevant less variables need to be opened synchronously. importStyle: 'less', unplugin-vue-components issues 160

AntDesignVueResolver({ importStyle: 'less' }) // It's important here

mock data

  • Vite plugin mock plug-in

vite plugin configuration

viteMockServe({
  ignore: /^_/,
  mockPath: 'mock',
  localEnabled: true,
  prodEnabled: false,
  // The development environment does not need to be concerned
  // injectCode is only affected by prodEnabled
  // https://github.com/anncwb/vite-plugin-mock/issues/9
  // The following code will be injected into main ts
  injectCode: `
      import { setupProdMockServer } from '../mock/_createProductionServer';

      setupProdMockServer();
      `,
})

Create under root directory_ createProductionServer.ts file

import { createProdMockServer } from 'vite-plugin-mock/es/createProdMockServer';

// Batch loading
const modules = import.meta.globEager('./**/*.ts');

const mockModules: any[] = [];
Object.keys(modules).forEach((key) => {
  if (key.includes('/_')) {
    return;
  }
  mockModules.push(...modules[key].default);
});

/**
 * Used in a production environment. Need to manually import all modules
 */
export function setupProdMockServer() {
  createProdMockServer(mockModules);
}

In this way, the non_ The first file will be automatically loaded into a mock file

For example:

import Mock from 'mockjs';

const data = Mock.mock({
  'items|30': [
    {
      id: '@id',
      title: '@sentence(10, 20)',
      account: '@phone',
      true_name: '@name',
      created_at: '@datetime',
      role_name: '@name',
    },
  ],
});

export default [
  {
    url: '/table/list',
    method: 'get',
    response: () => {
      const items = data.items;
      return {
        code: 0,
        result: {
          total: items.length,
          list: items,
        },
      };
    },
  },
];

After configuring the proxy, you can directly request / api/table/list to get the data

Proxy proxy

import proxy from './config/vite/proxy';

export default defineConfig({
  // server
  server: {
    hmr: { overlay: false }, // Disable or configure HMR connection settings server hmr. Overlay is false to disable the server error mask layer
    // Service configuration
    port: VITE_PORT, // Type: number specifies the server port;
    open: false, // Type: boolean | string automatically opens the application in the browser when the server starts;
    cors: false, // Type: boolean | CorsOptions configure CORS for the development server. Enable and allow any source by default
    host: '0.0.0.0', // Support IP initiated access
    proxy,
  },
})

The proxy is as follows

import {
  API_BASE_URL,
  API_TARGET_URL,
} from '../../config/constant';
import { ProxyOptions } from 'vite';

type ProxyTargetList = Record<string, ProxyOptions>;

const ret: ProxyTargetList = {
  // test
  [API_BASE_URL]: {
    target: API_TARGET_URL,
    changeOrigin: true,
    rewrite: (path) => path.replace(new RegExp(`^${API_BASE_URL}`), ''),
  },
  // mock
  // [MOCK_API_BASE_URL]: {
  //   target: MOCK_API_TARGET_URL,
  //   changeOrigin: true,
  //   rewrite: (path) => path.replace(new RegExp(`^${MOCK_API_BASE_URL}`), '/api'),
  // },
};

export default ret;

Environment variables env

I put the system configuration in config / constant TS management

In order to facilitate the management of interfaces and parameter configurations in different environments, environment variables can be used Env, e.g env,. env.local,. env.development,. env.production

It is very convenient to use with dotenv library

Package dependency analysis visualization

Plug in: rollup plugin visualizer

import visualizer from 'rollup-plugin-visualizer';

visualizer({
  filename: './node_modules/.cache/visualizer/stats.html',
  open: true,
  gzipSize: true,
  brotliSize: true,
})

Code compression

Plug in: vite plugin compression

import compressPlugin from 'vite-plugin-compression';

compressPlugin({
  ext: '.gz',
  deleteOriginFile: false,
})

Chunk unpacking

If you want to separate package dependencies such as ant design Vue, you can also manually configure the manualChunks property

// vite.config.ts
build: {
  rollupOptions: {
    output: {
      manualChunks: configManualChunk
    }
  }
}


// optimizer.ts
const vendorLibs: { match: string[]; output: string }[] = [
  {
    match: ['ant-design-vue'],
    output: 'antdv',
  },
  {
    match: ['echarts'],
    output: 'echarts',
  },
];

export const configManualChunk = (id: string) => {
  if (/[\/]node_modules[\/]/.test(id)) {
    const matchItem = vendorLibs.find((item) => {
      const reg = new RegExp(`[\/]node_modules[\/]_?(${item.match.join('|')})(.*)`, 'ig');
      return reg.test(id);
    });
    return matchItem ? matchItem.output : null;
  }
};

Compatible processing

Plug in: @ vitejs / plugin legacy

Compatible with browsers that do not support the < script type = "module" > feature, or IE browsers

// Native ESM
legacy({
  targets: ['defaults', 'not IE 11']
})

// IE11
// Regenerator runtime required
legacy({
  targets: ['ie >= 11'],
  additionalLegacyPolyfills: ['regenerator-runtime/runtime']
})

design sketch

home page

Visualization of package dependency analysis, partial screenshot

Start compression and compatibility to produce packaged products

Routing and layout

// router/index.ts
import { createRouter, createWebHashHistory } from 'vue-router'
import routes from './router.config'

const router = createRouter({
  history: createWebHashHistory(), //
  routes,
})

// main.ts
app.use(router); // Real columns can be used globally after mounting, such as < div @ Click = "$router. Push ('xx ')" > < / div >

The usage is as follows:

// router.config.ts
import BasicLayout from '/@/layouts/BasicLayout/index.vue'; // Basic layout
import BlankLayout from '/@/layouts/BlankLayout.vue'; // Empty layout
import type { RouteRecordRaw } from 'vue-router';

const routerMap: RouteRecordRaw[] = [
  {
    path: '/app',
    name: 'index',
    component: BasicLayout,
    redirect: '/app/home',
    meta: { title: 'home page' },
    children: [
      {
        path: '/app/home',
        component: () => import('/@/views/home/index.vue'),
        name: 'home',
        meta: {
          title: 'home page',
          icon: 'liulanqi',
          auth: ['home'],
        },
      },
      {
        path: '/app/others',
        name: 'others',
        component: BlankLayout,
        redirect: '/app/others/about',
        meta: {
          title: 'Other menus',
          icon: 'xitongrizhi',
          auth: ['others'],
        },
        children: [
          {
            path: '/app/others/about',
            name: 'about',
            component: () => import('/@/views/others/about/index.vue'),
            meta: { title: 'about', keepAlive: true, hiddenWrap: true },
          },
          {
            path: '/app/others/antdv',
            name: 'antdv',
            component: () => import('/@/views/others/antdv/index.vue'),
            meta: { title: 'assembly', keepAlive: true, breadcrumb: true },
          },
        ],
      },
    ]
  }
  ...
]

jurisdiction

  • Support page and menu level permission management and routing management
  • Support button level permission management
  • Support interface level permission management

Several key words: router Addroutes dynamic routing, v-auth instruction, axios interception

Use router Beforeeach global routing hook

The core logic is as follows. For details, see the warehouse code router / permission ts

// Failed to get, requested data
await permissioStore.fetchAuths();
// Filter permission routing
const routes = await permissioStore.buildRoutesAction();
// 404 route must be placed behind permission route
routes.forEach((route) => {
  router.addRoute(route);
});
// hack method
// next() is not used because after executing router After addroute,
// Routes that have not been added to the original routing table will No match
// replace enables the route to enter again and match it
next({ ...to, replace: true });

Use the v-auth command to control button level permissions

function isAuth(el: Element, binding: any) {
  const { hasPermission } = usePermission();

  const value = binding.value;
  if (!value) return;
  if (!hasPermission(value)) {
    el.parentNode?.removeChild(el);
  }
}

axios interception

Request interceptors in axios request. Use add

// Interface permission interception
const store = usePermissioStoreWithOut();
const { url = '' } = config;
if (!WhiteList.includes(url) && store.getIsAdmin === 0) {
  if (!store.getAuths.includes(url)) {
    return Promise.reject('No operation permission');
  }
}

summary

When vite + vue3 was first used, it was also a process of learning and developing while stepping on the pit. Fortunately, the community is more active now, and there are corresponding solutions to many problems. It is basically ok to eat together with documents and github issue. This project also refers to some implementations and code management of Vue vben admin. This paper is used as a learning record of vue3~

After using it, you will find that vue3 and vue2 have completely different development experiences. Now vue3 has excellent support for TS, the development efficiency and quality have increased to a higher level, and it also supports JSX syntax. Development in the form of React is also feasible. Of course, it also has great flexibility when used with Vue template, You can customize your own code according to the scene. In combination with the current script setup development, it's cool to take off~

When using the composition api development mode of vue3, we must abandon the development logic of the previous options api. With hooks, we can freely combine and split the code, which is highly flexible and convenient for maintenance and management. There will be no more screwing of the whole code in the era of vue2

In a word: vite + vue3 + setup + ts + vscode volar plug-in, who uses who knows, cool batch~

Warehouse address: https://github.com/JS-banana/vite-vue3-ts

reference resources

Topics: Javascript Front-end React html