vue3+ts+vite+pinia+element Plus+pnpm

2,649 阅读2分钟

链接地址:

版本号:

  • Node: v14.19.3
  • pnpm: 7.1.9
  • @vue/cli: 4.5.15

1. 项目基础环境搭建

命令: pnpm create vite vue3-ts-vite-pinia-element-plus-pnpm --template vue-ts 执行:

$ pnpm create vite vue3-ts-vite-pinia-element-plus-pnpm --template vue-ts
../../.pnpm-store/v3/tmp/dlx-13012       |   +6 +
Packages are hard linked from the content-addressable store to the virtual store.
  Content-addressable store is at: F:\.pnpm-store\v3
  Virtual store is at:             ../../.pnpm-store/v3/tmp/dlx-13012/node_modules/.pnpm
../../.pnpm-store/v3/tmp/dlx-13012       | Progress: resolved 6, reused 6, downloaded 0, added 6, done

Scaffolding project in F:\Web\Vue\vue3-ts-vite-pinia-element-plus-pnpm...

Done. Now run:

  cd vue3-ts-vite-pinia-element-plus-pnpm
  pnpm install
  pnpm run dev

命令: cd vue3-ts-vite-pinia-element-plus-pnpm/ pnpm install 执行:

$ pnpm install
Packages: +47
+++++++++++++++++++++++++++++++++++++++++++++++
Packages are hard linked from the content-addressable store to the virtual store.
  Content-addressable store is at: F:\.pnpm-store\v3
  Virtual store is at:             node_modules/.pnpm
Downloading registry.npmmirror.com/typescript/4.7.4: 11.7 MB/11.7 MB, done
node_modules/.pnpm/registry.npmmirror.com+esbuild@0.14.54/node_modules/esbuild: Running postinstall script, done in 398ms
Progress: resolved 68, reused 0, downloaded 47, added 47, done

dependencies:
+ vue 3.2.37

devDependencies:
+ @vitejs/plugin-vue 3.0.3
+ typescript 4.7.4
+ vite 3.0.8
+ vue-tsc 0.39.5 (0.40.1 is available)

命令: pnpm run dev 执行:

$ pnpm run dev

> vue3-ts-vite-pinia-element-plus-pnpm@0.0.0 dev F:\Web\Vue\vue3-ts-vite-pinia-element-plus-pnpm
> vite


  VITE v3.0.8  ready in 375 ms

  ➜  Local:   http://127.0.0.1:5173/Network: use --host to expose

点击链接,跳转浏览器查看效果:

image.png

至此项目基础框架搭建完毕

2. 必要依赖安装

element-plus

命令: pnpm install element-plus

执行:

$ pnpm install element-plus
Packages: +21
+++++++++++++++++++++
Downloading registry.npmmirror.com/element-plus/2.2.13: 7.85 MB/7.85 MB, done
node_modules/.pnpm/registry.npmmirror.com+vue-demi@0.13.8_vue@3.2.37/node_modules/vue-demi: Running postinstall script, done in 146ms
Progress: resolved 89, reused 47, downloaded 21, added 21, done

dependencies:
+ element-plus 2.2.13

@element-plus/icons-vue

命令: pnpm install @element-plus/icons-vue

执行:

$ pnpm install @element-plus/icons-vue
Already up-to-date
Progress: resolved 116, reused 95, downloaded 0, added 0, done

dependencies:
+ @element-plus/icons-vue 2.0.9

备注:

image.png

axios

命令: pnpm install axios

执行:

$ pnpm install axios
Packages: +8
++++++++
Progress: resolved 97, reused 68, downloaded 8, added 8, done

dependencies:
+ axios 0.27.2

pinia

命令: pnpm install pinia

执行:

$ pnpm install pinia
Packages: +2
++

dependencies:
+ pinia 2.0.19

Progress: resolved 99, reused 76, downloaded 2, added 2, done

vue-router

命令: pnpm install vue-router

执行:

$ pnpm install vue-router
Packages: +1
+
Progress: resolved 100, reused 79, downloaded 0, added 0, done

dependencies:
+ vue-router 4.1.3

sass

命令: pnpm install -D sass

执行:

$ pnpm install -D sass
Packages: +17 -1
+++++++++++++++++-
Progress: resolved 116, reused 79, downloaded 16, added 17, done

devDependencies:
+ sass 1.54.4
- vite 3.0.8
+ vite 3.0.

备注:

image.png

eslint

命令:pnpm install -D eslint

执行:

$ pnpm install -D eslint
Packages: +103
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Progress: resolved 219, reused 95, downloaded 103, added 103, done

devDependencies:
+ eslint 8.22.0

Next:

  1. 使用eslint自动进行配置

    命令:./node_modules/.bin/eslint --init

    执行:

    ----------------------------------- Questions ----------------------------------------
    $ ./node_modules/.bin/eslint --init
    You can also run this command directly using 'npm init @eslint/config'.
    npx: 40 安装成功,用时 3.221.
    ? How would you like to use ESLint? ... 
      To check syntax only
      To check syntax and find problems
    > To check syntax, find problems, and enforce code style
    2.
    ? What type of modules does your project use? ... 
    > JavaScript modules (import/export)
      CommonJS (require/exports)
      None of these
    3.
    ? Which framework does your project use? ... 
      React
    > Vue.js
      None of these
    4.
    ? Does your project use TypeScript? » No / >Yes
    5.
    ? Where does your code run? ...  (Press <space> to select, <a> to toggle all, <i> to invert selection)
    > √ Browser
    √ Node
    6.
    ? How would you like to define a style for your project? ... 
    > Use a popular style guide
      Answer questions about your style
    7.
    ? Which style guide do you want to follow? ... 
    > Standard: https://github.com/standard/eslint-config-standard-with-typescript
      XO: https://github.com/xojs/eslint-config-xo-typescript
    8.
    ? What format do you want your config file to be in? ... 
    > JavaScript
      YAML
      JSON
    
    Checking peerDependencies of eslint-config-standard-with-typescript@latest
    The config that you've selected requires the following dependencies:
    
    eslint-plugin-vue@latest eslint-config-standard-with-typescript@latest @typescript-eslint/eslint-plugin@^5.0.0 eslint@^8.0.1 eslint-plugin-import@^2.25.2 eslint-plugin-n@^15.0.0 eslint-plugin-promise@^6.0.0 typescript@*
    9.
    ? Would you like to install them now? » No / > Yes
    10.
    ? Which package manager do you want to use? ... 
      npm
      yarn
    > pnpm
    ----------------------------------- Result -------------------------------------
    $ ./node_modules/.bin/eslint --init
    You can also run this command directly using 'npm init @eslint/config'.
    npx: 40 安装成功,用时 3.22 秒
    √ How would you like to use ESLint? · style
    √ What type of modules does your project use? · esm
    √ Which framework does your project use? · vue
    √ Does your project use TypeScript? · No / Yes
    √ Where does your code run? · browser
    √ How would you like to define a style for your project? · guide
    √ Which style guide do you want to follow? · standard-with-typescript
    √ What format do you want your config file to be in? · JavaScript
    Checking peerDependencies of eslint-config-standard-with-typescript@latest
    The config that you've selected requires the following dependencies:
    
    eslint-plugin-vue@latest eslint-config-standard-with-typescript@latest @typescript-eslint/eslint-plugin@^5.0.0 eslint@^8.0.1 eslint-plugin-import@^2.25.2 eslint-plugin-n@^15.0.0 eslint-plugin-promise@^6.0.0 typescript@*
    √ Would you like to install them now? · No / Yes
    √ Which package manager do you want to use? · pnpm
    Installing eslint-plugin-vue@latest, eslint-config-standard-with-typescript@latest, @typescript-eslint/eslint-plugin@^5.0.0, eslint@^8.0.1, eslint-plugin-import@^2.25.2, eslint-plugin-n@^15.0.0, eslint-plugin-promise@^6.0.0, typescript@*
    Already up-to-date
    Progress: resolved 298, reused 277, downloaded 0, added 0, done
    Successfully created .eslintrc.cjs file in F:\Web\Vue\vue3-ts-vite-pinia-element-plus-pnpm
    
    

    result:

    1. 检查项目目录出现 .eslintrc.cjs 文件
    2. 检查 package.json,出现一堆eslint相关依赖
      "devDependencies": {
        "@typescript-eslint/eslint-plugin": "^5.0.0",
        ...
        "eslint": "^8.0.1",
        "eslint-config-standard-with-typescript": "^22.0.0",
        "eslint-plugin-import": "^2.25.2",
        "eslint-plugin-n": "^15.0.0",
        "eslint-plugin-promise": "^6.0.0",
        "eslint-plugin-vue": "^9.3.0",
        ...
    

prettier

命令: pnpm install -D --save-exact prettier

执行:

$ pnpm install -D --save-exact prettier
Packages: +1
+
Progress: resolved 220, reused 199, downloaded 0, added 0, done

devDependencies:
+ prettier 2.7.1

Next:

  1. 创建空的配置文件:echo {}> .prettierrc.json
  2. 创建prettier格式化忽略配置文件:.prettierignore
  3. .prettierignore 文件中添加如下初始配置
# Ignore artifacts:
build
coverage
  1. 配置与 eslint 配合(参考下一步)

备注:

1660895294303.png 1660895208290.png

eslint-config-prettier

命令: pnpm install -D eslint-config-prettier

执行:

$ pnpm install -D eslint-config-prettier
Packages: +1
+
Progress: resolved 299, reused 277, downloaded 1, added 1, done

devDependencies:
+ eslint-config-prettier 8.5.0

Next:

  1. prettier 添加到 .eslintrc.cjs 配置文件的 eslint 扩展配置中,并且保证其在该配置项的最后一项
/** ./.eslintrc.cjs */
module.exports = {
  env: {
    browser: true,
    es2021: true
  },
  extends: [
    'plugin:vue/vue3-essential',
    'standard-with-typescript',
    'prettier' <--------(此箭头仅起提示作用,代码中需要删除)
  ],
  ...

备注:

1660895639231.png

@types/node

命令: pnpm install -D @types/node

执行:

$ pnpm install -D @types/node
Packages: +1
+
Progress: resolved 300, reused 278, downloaded 1, added 1, done

devDependencies:
+ @types/node 18.7.6

备注:此依赖是node中的类型定义,在后面对于一些配置信息时需要使用node中的方法时,没有引入此依赖就会提示安装。

3. 基础配置

1. 添加环境变量配置文件

1. 根据 <开发版本> | <预发行版> | <生产版本> 创建单独的 .env 文件

  • .env : 基本配置项
  • .env.development : 开发环境配置项
  • .env.staging : 预发布版本配置项
  • .env.production : 生产环境配置项
# windows 批处理命令(在项目根目录cmd窗运行下面命令即可生成上述四个文件):
echo '# DEFAULT ENV'> .env;echo '# DEVELOPMENT ENV'> .env.development;echo '# STAGING DEV'> .env.staging;echo '# PRODUCTION ENV'> .env.production

2. 配置基本的配置项

修改 .env 文件

# DEFAULT ENV

# 项目标题
VITE_APP_TITLE = 'My App'

# 项目运行端口
VITE_PORT = '3000'

# 项目启动后自动打开默认浏览器
VITE_OPEN = 'true'

2. 修改 vite 配置文件 vite.config.js

  1. 设置模块引入路径别名 "@"

首先需要在 vite.config.js 添加别名和对应的路径信息

import { defineConfig, loadEnv } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path';

// https://vitejs.dev/config/
export default defineConfig(({command, mode}) => {
  return {
    resolve:{
      alias: {
        "@": resolve(__dirname, "./src")
      }
    },
    plugins: [vue()]
  }
})

然后在 tsconfig.json 文件中添加别名的配置

{
  "compilerOptions": {
    "target": "ESNext",
    "useDefineForClassFields": true,
    "module": "ESNext",
    "moduleResolution": "Node",
    "strict": true,
    "jsx": "preserve",
    "sourceMap": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "esModuleInterop": true,
    "lib": ["ESNext", "DOM"],
    "skipLibCheck": true,
    + "baseUrl": "./",
    + "paths": {
    +   "@/*": ["src/*"]
    + }
  },
  "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
  "references": [{ "path": "./tsconfig.node.json" }] // 此处注意后面会提到
}

  1. 将env文件中定义的配置添加到vite配置信息中
export default defineConfig(({command, mode}) => {
  
  const env = loadEnv(mode, process.cwd(), '');
  return {
    resolve:{
      alias: {
        "@": resolve(__dirname, "./src")
      }
    },
    server:{
      port: env.VITE_PORT, // 注意此时ts会提示VITE_PORT类型不符合
      open: env.VITE_OPEN
    },
    plugins: [vue()]
  }
})

由于ts无法识别.env中的配置信息的类型,默认都是string类型,所以需要添加类型声明文件

image.png

按照文档中提示,在项目的src目录下创建env.d.ts文件,由于创建项目的时候,已经自动在目录下生成了vite-env.d.ts,所以直接在此文件下添加,如果没有该文件,手动创建。

/// <reference types="vite/client" />

declare module '*.vue' {
  import type { DefineComponent } from 'vue'
  const component: DefineComponent<{}, {}, any>
  export default component
}

// +++
// * Vite Env 返回数据类型
declare type Recordable<T = any> = Record<string, T>;


interface ViteEnv {
  readonly VITE_APP_TITLE: string;
  readonly VITE_PORT: number;
  readonly VITE_OPEN: boolean;
  // 更多环境变量...
}

interface ImportMeta {
  readonly env: ViteEnv
}
// +++

添加了上面的信息后,回到vite.config.ts中,依然提示上面类型问题,所以此处写一个转换函数,将结果输出为我们定义的类型

在src目录下创建utils目录,然后创建getEnv.ts

import fs from "fs";
import path from "path";


// 在 .env 文件中读取配置信息,并转换为 vite-env.d.ts 文件中声明类型的值
export function wrapperEnv(env: Recordable): ViteEnv {
    const ret: any = {};
    for (const envName of Object.keys(env)) {
        let envValue = env[envName].replace(/\\n/g, "\n");
        envValue = envValue === "true" ? true : envValue === "false" ? false : envValue;

        if (envName === "VITE_PORT") {
            envValue = Number(envValue);
        }

        ret[envName] = envValue;
        process.env[envName] = envValue;
    }
    return ret;
}

添加了上面内容后,发现在vite.config.ts中无法引入此wrapperEnv函数,ts无法自动识别并提示,同时手动添加导入信息会提示

image.png

按照提示的信息,找到项目根目录下的tsconfig.node.json文件,发现其与tsconfig.json是一样的,只不过里面的配置信息不相同,这个文件是为了typescript在node环境的配置信息

上文提到的tsconfig.json文件中的最后一行中的 references

image.png

其作用是可以将程序分割为更小的模块,然后统一进行管理和编译,因为我并不了解所以具体内容自行查阅, 而此处主要使用用于对node环境下,运行的vite.config.ts进行的配置,在文件中填写包含的文件即创建的getEnv.ts,就可以在vite.config.ts中正常的引入getEnv函数了,同时也可以自动导入函数。

{
  "compilerOptions": {
    "composite": true,
    "module": "ESNext",
    "moduleResolution": "Node",
    "allowSyntheticDefaultImports": true
  },
  "include": [
      "vite.config.ts",
      + "./src/utils/getEnv.ts"
  ]
}

修改后的vite.config.ts

import { defineConfig, loadEnv } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path';
import { wrapperEnv } from './src/utils/getEnv';

// https://vitejs.dev/config/
export default defineConfig(({command, mode}) => {
  
  const env = loadEnv(mode, process.cwd(), '');
  const viteEnv = wrapperEnv(env);
  return {
    resolve:{
      alias: {
        "@": resolve(__dirname, "src")
      }
    },
    server:{
      port: viteEnv.VITE_PORT,
      open: viteEnv.VITE_OPEN
    },
    plugins: [vue()]
  }
})

此时再次再次运行 pnpm run dev 即可发现项目启动后自动打开默认浏览器并跳转到localhost:3000

4. 配置 element-plus

  1. 全局注册element-plus组件(因为我现在并不是很在乎打包大小) 011B055E.png

1661147666394.png

  1. 将element-plus组件默认语言设置为中文简体

1661148179946.png

  1. 将element的icon组件全局注册 1661147643641.png

代码:

import { createApp } from 'vue'
import ElementPlus from 'element-plus'
import zhCn from 'element-plus/es/locale/lang/zh-cn'
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
import 'element-plus/dist/index.css'
import './style.css'
import App from './App.vue'


const app = createApp(App);

// 注册 Element-Plus
app.use(ElementPlus, {
  locale: zhCn,
});
// 注册 Element-Plus Icon 组件
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
  app.component(key, component)
}

app.mount('#app');


5. 创建 项目目录

vue3-ts-vite-pinia-element-plus-pnpm
├── .env
├── .env.development
├── .env.production
├── .env.staging
├── .eslintrc.cjs
├── .gitignore
├── .prettierignore
├── .prettierrc.json
├── .vscode
│   └── extensions.json
├── README.md
├── dist
│   ├── assets
│   ├── index.html
│   └── vite.svg
├── index.html
├── node.md
├── package.json
├── pnpm-lock.yaml
├── public
│   └── vite.svg
├── src
│   ├── App.vue
│   ├── api
│   ├── assets
│   ├── components
│   ├── config
│   ├── hooks
│   ├── layout
│   ├── main.ts
│   ├── plugins
│   ├── routers
│   ├── style.css
│   ├── types
│   ├── utils
│   ├── views
│   └── vite-env.d.ts
├── tsconfig.json
├── tsconfig.node.json
└── vite.config.ts

1661156441842.png

如何生成文件树

使用 tree-node-cli

1. 全局安装

npm install -g tree-node-cli

2. 运行命令,由于windows环境为了与默认tree冲突,需要使用treee的命令执行

treee -L 2 -I "node_modules" > node.md

  • a. -L 2 : 指层级为两层
  • b. -I "node_modules" : 忽略node_modules文件夹
  • c. > node.md: 指将结果输出到node.md文件中

6. 配置 vue-router

1. 在 src/routers 创建index.ts

2. 在 src/views 添加两个用于跳转展示的页面 Home.vue, About.vue

<template>
  <div>Home</div>
</template>

<script setup lang="ts"></script>

<style scoped></style>

About.vue与之相同

3. 在router/index.ts中添加路由内容

import AboutVue from "@/views/About.vue"
import HomeVue from "@/views/Home.vue"
import { createRouter, createWebHashHistory } from "vue-router"

// 每个路由都需要映射到一个组件。
const routes = [
  { path: '/', component: HomeVue },
  { path: '/about', component: AboutVue },
]

// 创建路由实例并传递 `routes` 配置
const router = createRouter({
  // 使用 hash 模式。
  history: createWebHashHistory(),
  routes, // `routes: routes` 的缩写
})

export default router

4. 修改App.vue,用于展示跳转页面的内容

<template>
  <div>
    <RouterLink to="/">Home</RouterLink>
    <br/>
    <RouterLink to="/about">About</RouterLink>
  </div>
  <router-view/>
</template>

<script setup lang="ts"></script>

<style scoped></style>

5. 修改main.ts,注册router的信息

...
+ import router from '@/routers/index'

const app = createApp(App);

+ app.use(router);
...

6. 访问页面就可以体验简单的路由跳转功能

1661155803680.png

1661155787230.png

遇到的问题:

1. 创建的Home.vue会被eslint提示命名不符合规范

1661155560440.png 原因是vue在创建项目的时候使用了vue/cli-plugin-eslint插件,其在 eslint-plugin-vue v7.20.0后添加了除了根目录的app.vue和vue提供的组件以外,组件命名需要至少两个单词组成,即类似于HomeVue的形式,用来防止用户命名不规范的问题,但是我们这里不需要这种,所以需要将其验证条件关闭

1661154978759.png

关闭方式,打开根目录下的 .eslintrc.cjs 文件,在rules中添加关闭代码,然后问题解决

module.exports = {
  env: {
    browser: true,
    es2021: true
  },
  extends: [
    'plugin:vue/vue3-essential',
    'standard-with-typescript',
    'prettier'
  ],
  overrides: [
  ],
  parserOptions: {
    ecmaVersion: 'latest',
    sourceType: 'module'
  },
  plugins: [
    'vue'
  ],
  rules: {
    + "vue/multi-word-component-names":"off",
  }
}

7. 配置 pinia

1. 先将pinia注册到vue中

...
+ import { createPinia } from 'pinia'

app.use(router);
+ app.use(createPinia());
...

1661159930136.png

2. 在src/stores 创建index.ts

// stores/index.js
import { defineStore } from 'pinia'

export const useGlobalStore = defineStore('GlobalState', {
  state: () => {
    return { count: 0 }
  },
  actions: {
    add() {
      this.count++
    },
    sub(){
      this.count--
    }
  },
  getters: {
    double(state){
      return state.count * 2;
    }
  }
})
  • state:存储数据
  • actions:定义对数据进行操作的方法
  • getters:定义对数据进行计算的方法

3. 在Home.vue中使用store

<template>
  <div>Home</div>
  <div>Count: {{count}}</div>
  <div>Double: {{double}}</div>
  <el-button @click="globalStore.add">+</el-button>
  <el-button @click="globalStore.sub">-</el-button>
</template>

<script setup lang="ts">
import { useGlobalStore } from '@/stores';
import { storeToRefs } from 'pinia';
  const globalStore = useGlobalStore()
  const {count, double} = storeToRefs(globalStore) // !!!
</script>

<style scoped></style>

注意:

此处需要注意的是,从store中可以直接获取state中定义的数值,但是这个数据并不能成为响应式数据,具体表现为数值发生变化时,直接获取形式的数值并不会发生变化,所以需要使用pinia提供的storeToRefs函数,将数值转换为响应式的数值

4. 在About.vue中展示store中的值

<template>
  <div>About</div>
  <div>Count: {{count}}</div>
  <div>Double: {{double}}</div>
</template>

<script setup lang="ts">
import { useGlobalStore } from '@/stores';
import { storeToRefs } from 'pinia';
  const globalStore = useGlobalStore()
  const {count, double} = storeToRefs(globalStore)
</script>

<style scoped></style>

5. 效果

1661160386950.png

1661160407400.png

8. scss

sass安装好后,就可以使用scss了

<template>
  <div>Home</div>
  <ul>
    <li class="count">Count: {{count}}</li>
    <li class="double">Double: {{double}}</li>
  </ul>
  <el-button @click="globalStore.add">+</el-button>
  <el-button @click="globalStore.sub">-</el-button>
</template>

<script setup lang="ts">
import { useGlobalStore } from '@/stores';
import { storeToRefs } from 'pinia';
  const globalStore = useGlobalStore()
  const {count, double} = storeToRefs(globalStore)
</script>

<style scoped lang="scss">
$color: red;
ul{
  color: $color;
}
</style>

image.png

9. 配置 axios