掘金文章归档 · 第 21 篇 Vue3 / Vite 系列 4/4 工程搭建

vite+vue3+ts+eslint+pinia+tailwindcss+element-plus 初始化项目

从零搭建前端项目:Vite → Router → Pinia → Element Plus → Tailwind 适配 → ESLint

原文作者:随意_(掘金) | juejin.cn/post/7358702198588473382 | 发布于 2024-04-17 | 阅读 1,638 · 约 7 分钟

0背景与项目技术栈

最近有朋友要搭建一个 vue3+ts 的项目,本着帮一把的原则,搭建了一个,仅仅作为参考(市场有很多成熟框架如 admin 等)。好记性不如烂笔头,记录如下。

📌 技术栈:Vite + Vue3 + TS + ESLint + Pinia + Tailwind CSS + Element Plus

1Vite 初始化 + 别名与代理配置

1.1 Vite 初始化项目

npm create vite

输入文件名称,回车即可,默认名称为 vite-project;选择 vue + ts 回车,创建成功。

cd vite-tets-work
npm install
npm run dev

安装依赖,dev 项目启动成功,默认 http://localhost:5173/ 端口。

1.2 安装 sass 与 @types/node

npm i -D sass
npm i @types/node -D

@types/node 解决 vite.config.ts 中 import path from "node:path" 的 TS 报错问题。

1.3 设置别名、server 代理及端口

import { defineConfig } from 'vite'
import path from "node:path";
import vue from '@vitejs/plugin-vue'

export default defineConfig({
  plugins: [
    vue()
  ],
  base: "./",
  resolve: {
    alias: {
      "@": path.resolve(__dirname, "./src"),
    },
  },
  // 配置代理
  server: {
    host: "0.0.0.0",
    port: 8861,
    open: true,
    proxy: {
      "/api": {
        target: "***********",
        changeOrigin: true,
        secure: false,
        // rewrite: (path) => path.replace(/^\/api/, ""),
      }
    },
    cors: true,
  },
})

1.4 tsconfig.json 修改(@ 别名支持)

{
  "compilerOptions": {
    "target": "ES2020",
    "useDefineForClassFields": true,
    "module": "ESNext",
    "lib": ["ES2020", "DOM", "DOM.Iterable"],
    "skipLibCheck": true,

    /* Bundler mode */
    "moduleResolution": "bundler",
    "allowImportingTsExtensions": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "preserve",

    /* Linting */
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noFallthroughCasesInSwitch": true,

    // @设置
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"]
    },
  },
  "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue", "src/**/*.d.ts"],
  "references": [{ "path": "./tsconfig.node.json" }]
}

2Vue Router 路由

npm i vue-router -S

创建 router 文件夹,内部创建 index.ts:

import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router';
import TestRoutes from '@/views/test/routes';

const routes: Array<RouteRecordRaw> = [
  {
    path: '/',
    redirect: '/login'
  },
  {
    path: '/login',
    name: 'Login',
    component: () => import('@/views/login/index.vue')
  },
  {
    path: '/test',
    name: 'test',
    children: [...TestRoutes],
    meta: {
      sort: 1,
      icon: 'FillSet',
      title: '测试带单',
      menu: true
    }
  }
];
const router = createRouter({
  scrollBehavior(to, from, savedPosition) {
    return { top: 0 };
  },
  history: createWebHistory(),
  routes
});

router.beforeEach((to: any, from, next) => {
  next();
});

export default router;

创建 views 文件夹,下一级创建 login、test 文件夹,test 中的 routes.ts 如下:

import { RouteRecordRaw } from 'vue-router';

const routes: Array<RouteRecordRaw> = [
  {
    path: 'test1',
    name: 'test1',
    component: () => import('@/views/test/test1.vue'),
    meta: {
      title: '测试1'
    }
  },
  {
    path: 'test2',
    name: 'test2',
    component: () => import('@/views/test/test2.vue'),
    meta: {
      title: '测试2'
    }
  }
];

export default routes;

main.ts 引用路由,App.vue 引入 <router-view/>

import { createApp } from "vue";
import "./style.css";
import App from "./App.vue";

// routes
import router from "./router/index";

const app = createApp(App);
app.use(router);
app.mount("#app");
<template>
  <div>
    <router-view/>
  </div>
</template>

<style scoped>
.logo {
  height: 6em;
  padding: 1.5em;
  will-change: filter;
  transition: filter 300ms;
}
.logo:hover {
  filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.vue:hover {
  filter: drop-shadow(0 0 2em #42b883aa);
}
</style>
⚠️ 此时如果报 TS 找不到模块"vue-router"或其相应的类型声明,重新打开下 VSCode 即可。重新 npm run dev,默认 http://localhost:8861/login 打开 login 组件,/test/test1 打开 test1 组件。

3Pinia 状态管理(含持久化)

npm i pinia -S
npm i pinia-plugin-persistedstate -S

pinia-plugin-persistedstate 用于持久化 pinia。创建 store 文件夹,内部创建 use-user.ts:

// src/stores/counter.ts
import { defineStore } from 'pinia';
import { reactive } from 'vue';

export const useUserInfoStore = defineStore(
  'userInfo',
  () => {
    const user = reactive({
      name: '',
      age: '',
      sex: ''
    });

    const setUserInfo = (data: { name: string, age: string, sex: string }) => {
      Object.assign(user, data);
    };

    return { user, setUserInfo };
  },
  {
    persist: true,
  }
);

login 中的 index.vue:

<template>
  <div>
    <span class="text-[#ff0000]">{{ user }}</span>
    <button @click="addStore">点击</button>
  </div>
</template>
<script lang="ts" setup>
import { storeToRefs } from "pinia";
import { useUserInfoStore } from "@/store/use-user";

const userInfoStore = useUserInfoStore();

const { user } = storeToRefs(userInfoStore);

const addStore = () => {
  userInfoStore.setUserInfo({
    name: "张三",
    age: Math.random() * 100 + "",
    sex: "男"
  });
};
</script>

test 中的 test1.vue:

<template>
  <div class="empty">
    {{ JSON.stringify(user) }}
  </div>
</template>

<script lang="ts" setup>
import { useUserInfoStore } from '@/store/use-user';
import { storeToRefs } from 'pinia';

const userInfoStore = useUserInfoStore();

const { user } = storeToRefs(userInfoStore);
</script>

main.ts 中引入 pinia:

import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import { createPinia } from 'pinia';
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate';

//routes
import router from "./router/index";

const pinia = createPinia();
pinia.use(piniaPluginPersistedstate);

const app= createApp(App)

app.use(pinia);
app.use(router);
app.mount('#app');
📌 此时 login 组件操作可修改 pinia,test1 组件会获取到 pinia 状态。TS 报错 store 找不到时重启 VSCode 即可。

4Element Plus 自动导入

npm i element-plus @element-plus/icons-vue -S
npm install unplugin-vue-components unplugin-auto-import -D

unplugin-vue-components / unplugin-auto-import 自动导入插件,可以按需引入(不用此插件也可以)。vite.config.ts 做更改:

import { defineConfig } from 'vite'
import path from "node:path";
import vue from '@vitejs/plugin-vue'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
import AutoImport from "unplugin-auto-import/vite"
import Components from 'unplugin-vue-components/vite';

export default defineConfig({
  plugins: [
    vue(),
    Components({
      resolvers: [ElementPlusResolver()],
      dts: "src/components.d.ts",
    }),
    AutoImport({
      imports: ['vue', 'vue-router'],
      dts: "src/auto-import.d.ts",
    })
  ],
  base: "./",
  resolve: {
    alias: {
      "@": path.resolve(__dirname, "./src"),
    },
  },
  server: {
    host: "0.0.0.0",
    port: 8861,
    open: true,
    proxy: {
      "/api": {
        target: "***********",
        changeOrigin: true,
        secure: false,
      }
    },
    cors: true,
  },
})

login 组件中直接使用 <el-button> 即可,可见 element-plus 已经按需引入,button 组件正常展示。

<template>
  <div>
    <span>{{ user }}</span>
    <button @click="addStore">点击</button>
    <el-button type="primary">Primary</el-button>
    <el-button type="success">Success</el-button>
    <el-button type="info">Info</el-button>
    <el-button type="warning">Warning</el-button>
    <el-button type="danger">Danger</el-button>
  </div>
</template>

5Tailwind CSS 安装与适配原理

npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p

两步骤操作,根目录生成 postcss.config.js:

export default {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  },
}

tailwind.config.js:

/** @type {import('tailwindcss').Config} */
export default {
  content: [],
  theme: {
    extend: {},
  },
  plugins: [],
}

创建 styles 文件夹,下级创建 index.css、tailwind.css:

/* index.css */
@import url('tailwind.css');
/* tailwind.css */
@tailwind base;
@tailwind components;
@tailwind utilities;

main.ts 引入 tailwindcss,tailwind.config.js 做如下更改(自定义颜色与字号、darkMode、关闭 preflight):

/** @type {import('tailwindcss').Config} */
export default {
  darkMode: "class",
  corePlugins: {
    preflight: false
  },
  content: ["./index.html", "./src/**/*.{vue,js,ts,jsx,tsx}"],
  theme: {
    extend: {
      colors: {
        title: "#202020",
        secondTitle: "#8A8A8A",
        normal: "#4b4b4b",
        light: "#dddddd",
        active: "#2C54D1",
        disabled: "#aaaaaa",
      },
      fontSize: {
        20: "20px",
        18: "18px",
        16: "16px",
        14: "14px",
      },
    },
  },
  plugins: [],
}
⚠️ 如果 tailwind.css 报错 Unknown at rule @tailwindcss(unknownAtRules),在根目录创建 .vscode 下级的 settings.json,可消除报错:{"css.lint.unknownAtRules": "ignore"}

5.1 适配原理

tailwindcss 尺寸都是基于 rem 的:谷歌浏览器 html 的 font-size: 16px,所以 1rem 对应 16px。基于此我们可以根据屏幕宽度对应根文字大小,tailwindcss 可基于 html 的 font-size 来适配。在 html 文件中增加如下代码,默认 1920 是 16px,动态计算宽度来适配:

<script>
    window.addEventListener("resize", () => {
      const screenWidth = window.innerWidth;
      const baseFontSize = 16; // 当屏幕宽度为1920px时的字体大小
      const baseScreenWidth = 1920;

      const fontSize = (screenWidth / baseScreenWidth) * baseFontSize;

      // 假设你想要改变的是body元素的字体大小
      document.documentElement.style.fontSize = `${fontSize}px`;
    });

    // 初始设置
    window.dispatchEvent(new Event("resize"));
  </script>

5.2 安装 postcss-pxtorem,将 px 转为 rem

npm i postcss-pxtorem -S

postcss.config.js 更改为:

export default {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
    "postcss-pxtorem": {
      rootValue: 16, // 根元素字体大小或根据input参数返回
      unitPrecision: 5, // 允许 REM 单位增长到的十进制数字
      propList: ["*"], // 可以从 px 更改为 rem 的属性(通配符*启用所有属性)
      selectorBlackList: [], // 要忽略并保留为 px 的选择器
      replace: true, // 替换包含 rems 的规则,而不是添加回退
      minPixelValue: 0, // 最小的转化单位
      exclude: /node_modules/i, // 要忽略并保留为 px 的文件路径
    },
  },
};
✅ 这样可按照标准设计稿 1920 对应的尺寸来开发了,如设计稿 120px 可写为 tailwindcss 的 w-[120px]

6ESLint 与 Axios & 结尾

恰逢 eslint@9 及扁平化配置出来,技术有限配置了半天没搞定扁平化配置,还是使用非扁平化的配置方式。package.json 中 devDependencies 增加如下依赖:

"@typescript-eslint/parser": "^7.7.0",
"@vue/eslint-config-standard": "^8.0.1",
"@vue/eslint-config-typescript": "^13.0.0",
"eslint": "^8.57.0",
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^6.1.1",
"eslint-plugin-vue": "^9.25.0",
"vite-plugin-eslint": "^1.8.1",
"prettier-eslint": "^16.3.0",

重新 npm i 安装依赖。根目录创建 .eslintignore:

**/iconfont.js
**/flexable.js
src/apis/modules/*
src/apis/**/model.ts
node_modules

.eslintrc.cjs:

module.exports = {
  root: true,
  env: {
    node: true,
  },
  extends: [
    "plugin:vue/vue3-essential",
    "@vue/standard",
    "@vue/typescript/recommended",
  ],
  parserOptions: {
    ecmaVersion: 2020,
  },
  ignorePatterns: [
    "*.config.*",
    "dist/*",
    "build/*",
    "public/*",
    "src/apis/modules/**",
  ],
  rules: {
    "no-console": "error"
  },
  globals: {
    uap: "readonly",
    uni: "readonly",
    regist: "readonly",
    defineProps: "readonly",
    defineOptions: "readonly",
    defineEmits: "readonly",
    defineExpose: "readonly",
  },
};

打开 VSCode 安装 eslint 插件,打开保存即可。在 test/test1.vue 写一句 console.log(1111),eslint 已经生效。

安装 axios

npm i axios -S

关于 axios 一个封装请求,需要根据自己的业务需要特定封装,官方文档如下 axios。

✅ 结尾:好了,结束。以上为项目搭建全流程记录,仅作参考。