vue3 项目构建

1.项目基础搭建

1.1 使用 vite 搭建 Vue3 项目

基础条件

已安装 16.0 或更高版本的 Node.js

初始化项目

npm create vite@latest

或者

yarn create vite
  • 输入项目名称:vue3-vite-ts-demo
  • 输入项目框架:Vue
  • 输入项目语言:TypeScript
success Installed "create-vite@4.1.0" with binaries:
      - create-vite
      - cva
√ Project name: ... vue3-vite-ts-demo
√ Select a framework: » Vue
√ Select a variant: » TypeScript

安装依赖

cd vue3-vite-ts-demo && yarn

项目初始化完成

  |-- vue3-vite-ts-demo
  |   |-- .gitignore
  |   |-- index.html
  |   |-- package.json
  |   |-- README.md
  |   |-- tsconfig.json
  |   |-- tsconfig.node.json
  |   |-- vite.config.ts
  |   |-- .vscode
  |   |   |-- extensions.json
  |   |-- public
  |   |   |-- vite.svg
  |   |-- src
  |       |-- App.vue
  |       |-- main.ts
  |       |-- style.css
  |       |-- vite-env.d.ts
  |       |-- assets
  |       |   |-- vue.svg
  |       |-- components
  |           |-- HelloWorld.vue

增加 vscode 插件TypeScript Vue Plugin (Volar)

屏蔽 vscode 插件vetur

1.2 优化 tsconfig.json

1.2.1 添加 node 类型识别

避免 vite.config.ts 中不能识别 nodeJs 的代码

yarn add @types/node -D
"compilerOptions": {
    "types": [
      "node"
    ],
}

1.2.2 添加别名'@'路径识别

"compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": [
        "src/*"
      ]
    },
}

1.2.3 取消部分严格语法的校验

"compilerOptions": {
  // 不使用的变量
  "noUnusedLocals": false,
  // 不使用的参数
  "noUnusedParameters": false,
  // 禁止未申明时类型默认为any
  "noImplicitAny": false,
  // 禁止未申明时this
  "noImplicitThis": false,
}

1.2.4 排除对构建代码的编译

 /* "exclude"指定哪些文件不包含
     "*" 表示任意文件
     "**" 表示任意目录
  */
  "exclude": [
    "publish/*",
    "vite.config.ts"
  ],

1.3 添加 eslint

1.3.1 安装包 eslint eslint-plugin-vue

yarn add -D eslint eslint-plugin-vue

1.3.2 初始化 eslInt 配置

npm init @eslint/config
√ 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? · prompt
√ What format do you want your config file to be in? · JavaScript
√ What style of indentation do you use? · tab
√ What quotes do you use for strings? · single
√ What line endings do you use? · unix
√ Do you require semicolons? · No / Yes

之后生成文件 .eslintrc.js

  • 增加 extends:"plugin:prettier/recommended",对 prettier 的支持
  • 增加排除路径 ignorePatterns: ['publish/**/*', 'vite.config.ts', '.prettierrc.js', '.eslintrc.js'],
  • 增加 rules 根据需要
module.exports = {
  env: {
    browser: true,
    es2021: true,
  },
  extends: [
    "eslint:recommended",
    "plugin:@typescript-eslint/recommended",
    "plugin:vue/vue3-essential",
    "plugin:prettier/recommended",
  ],
  overrides: [
    {
      env: {
        node: true,
      },
      files: [".eslintrc.{js,cjs}"],
      parserOptions: {
        sourceType: "script",
      },
    },
  ],
  ignorePatterns: [
    "publish/**/*",
    "vite.config.ts",
    ".prettierrc.js",
    ".eslintrc.js",
  ],
  parserOptions: {
    ecmaVersion: "latest",
    parser: "@typescript-eslint/parser",
    sourceType: "module",
  },
  plugins: ["@typescript-eslint", "vue"],
  rules: {
    indent: ["off", 2],
    "linebreak-style": ["error", "unix"],
    quotes: ["error", "single"],
    semi: ["error", "always"],
    "no-unused-vars": "off",
    "@typescript-eslint/no-unused-vars": "off",
    "@typescript-eslint/no-explicit-any": "off",
    "no-empty": "off",
    "vue/multi-word-component-names": "off",
  },
};

"semi"和"quotes"是 ESLint 中规则的名称。第一个值是规则的错误级别,可以是以下值之一:

  • "off"或 0 -关闭规则
  • "warn"或 1 -将规则作为警告打开(不影响退出代码)
  • "error"或 2 -将规则作为错误打开(退出代码将为 1)
  • 这三个错误级别允许你细粒度地控制 ESLint 如何应用规则。

1.3.3 配置 VS Code

vscode 插件里边搜索 ESLint 插件

1.3.4 添加其他插件

yarn add @typescript-eslint/parser

1.3.5 增加 lint 命令

"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",

1.4 添加 prettier

vscode 插件里边搜索 prettier 插件

yarn add prettier

添加其他插件

yarn add eslint-config-prettier eslint-plugin-prettier

配置.prettierrc.js 文件:

  • useTabs:使用 tab 缩进还是空格缩进,选择 false 时表示使用空格;
  • tabWidth:tab 是空格的情况下,是几个空格,选择 2 个;
  • printWidth:当行字符的长度,推荐 80;
  • singleQuote:使用单引号还是双引号,选择 true,使用单引号;
  • trailingComma:在多行输入的尾逗号是否添加,设置为 none;
  • semi:语句末尾是否要加分号,默认值 true,选择 false 表示不加;
  • endOfLine:结尾符号 lf
module.exports = {
  useTabs: true,
  printWidth: 80,
  singleQuote: true,
  trailingComma: "none",
  semi: true,
  endOfLine: "lf",
};

添加 vscode prettier 插件

在项目根目录添加.vscode/settings.json

{
  "editor.defaultFormatter": "esbenp.prettier-vscode",
  "editor.formatOnPaste": false, // required
  "editor.formatOnType": false, // required
  "editor.formatOnSave": true, // optional
  "editor.formatOnSaveMode": "file", // required to format on save
  "files.autoSave": "onFocusChange", // optional but recommended
  "[typescript]": {
    "editor.defaultFormatter": "dbaeumer.vscode-eslint"
  },
  "[vue]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  }, // set as "true" to run 'prettier' last not first
  "[javascript]": {
    "editor.defaultFormatter": "dbaeumer.vscode-eslint"
  },
  "eslint.validate": [
    "javascript",
    "typeScript",
    "javascriptreact",
    "vue"
  ],
}

在项目根目录 **package.json** 的 **script** 添加 **format** 命令

```sh
 "format": "prettier --write \"src/**/*.ts\" \"src/**/*.vue\" ",

1.5 增加环境变量

根目录增加各种模式的环境变量文件:

// .env.local-mode文件
VITE_APP_API_URL_MODE = "local";
VITE_APP_API_DOMAIN = "http://localhost:9010";
// .env.local-test文件
VITE_APP_API_URL_MODE = "test";
VITE_APP_API_DOMAIN = "http://test.api.xxx.com";
// .env.local-prod文件
VITE_APP_API_URL_MODE = "prod";
VITE_APP_API_DOMAIN = "http://api.xxx.com";

package.json的文件中的script增加命令

"script":{
"local-dev": "vite --mode local-mode",
"test-dev": "vite --mode test-mode",
"prod-dev": "vite --mode prod-mode",
"test-build": "vue-tsc && vite build --mode test-mode",
"prod-build": "vue-tsc && vite build --mode prod-mode"
}

在命令行加载 --mode local-mode 的时候就会加载文件 .env 与.env-local

在 js 实际代码中就能通过环境变量 import.meta.env.VITE_APP_API_URL_MODE读取模式

通过环境变量 import.meta.env.VITE_APP_API_DOMAIN读取 url 值

const mode = import.meta.env.VITE_APP_API_URL_MODE || "local";
const apiUrlPrefix = import.meta.env.VITE_APP_API_DOMAIN + "/";

vite 的环境变量:为了防止意外地将一些环境变量泄漏到客户端,只有以 VITE_ 为前缀的变量才会暴露给经过 vite 处理的代码。

Vite 在一个特殊的 import.meta.env 对象上暴露环境变量。这里有一些在所有情况下都可以使用的内建变量:

import.meta.env.MODE: {string} 应用运行的模式。

import.meta.env.BASE_URL: {string} 部署应用时的基本 URL。他由base 配置项决定。

import.meta.env.PROD: {boolean} 应用是否运行在生产环境。

import.meta.env.DEV: {boolean} 应用是否运行在开发环境 (永远与 import.meta.env.PROD相反).env.[mode]         # 只在指定模式下加载

1.6 修改 vite.config.ts

首先添加@types/node,便于识别vite.config.ts文件,否则文件中的 path 模块无法识别类型

yarn add -D @types/node
# 或者
npm i -D @types/node

自动导入:首先你需要安装unplugin-vue-componentsunplugin-auto-import这两款插件

yarn add -D unplugin-vue-components unplugin-auto-import
# 或者
npm i -D unplugin-vue-components unplugin-auto-import

1.6.1 Vant 版本情况

  • 添加别名@
  • 添加 less,less 文件中注入 less 代码 @import '@/style/variable.less' ,相当于增加全部 less 变量
  • 修改本地开发的端口为 8010
  • 设置 http 代理,有需要时候设置

增加环境变量文件 .env.local-mode

// env.local-mode

VITE_APP_API_URL_MODE = "local"; // 有无分号均可
VITE_APP_API_DOMAIN = "http://localhost:9010";

package.json中增加local-mode的相关脚本

// package.json
{
  // ...
  "scripts": {
    "dev": "vite",
    // 对应.env.local-mode文件
    "local-dev": "vite --mode local-mode"
  }
}
import vue from "@vitejs/plugin-vue";
import Components from "unplugin-vue-components/vite";
import { VantResolver } from "unplugin-vue-components/resolvers";
import { defineConfig, loadEnv, ConfigEnv, UserConfig } from "vite";
import eslint from "vite-plugin-eslint";
import path from "path";

export default defineConfig(({ mode }: ConfigEnv): UserConfig => {
  const root = process.cwd();

  /*当运行"vite --mode local-mode", mode的值为"local-mode" */
  const env = loadEnv(mode, root); // 环境变量对象

  /****当运行"vite --mode local-mode"时:
   *  env的值为:
   *  {
   *    VITE_APP_API_URL_MODE: '"local";',
   *    VITE_APP_API_DOMAIN: '"http://localhost:9010";'
   *  }
   *
   */
  const { VITE_APP_API_DOMAIN } = env;
  const proxyTargetUrl = VITE_APP_API_DOMAIN;

  return {
    plugins: [
      vue(),
      eslint(),
      Components({
        resolvers: [VantResolver()],
      }),
    ],
    resolve: {
      //设置别名
      alias: {
        "@": path.resolve(__dirname, "src"),
      },
    },
    css: {
      preprocessorOptions: {
        less: {
          additionalData: "@import '@/style/variable.less';",
        },
      },
    },
    server: {
      port: 8010, //启动端口
      hmr: {
        host: "localhost",
        port: 8010,
      },
      // 设置 https 代理
      proxy: {
        "/api": {
          target: proxyTargetUrl,
          changeOrigin: true,
          // rewrite: (path: string) => path.replace(/^\/api/, 'api/')
        },
      },
    },
  };
});

1.6.2 Element-plus 版本情况

  • 添加别名@
  • 添加 less,less 文件中注入 less 代码 @import '@/style/variable.less' ,相当于增加全部 less 变量
  • 修改本地开发的端口为 8010
  • 设置 http 代理,有需要时候设置
import vue from "@vitejs/plugin-vue";
import Components from "unplugin-vue-components/vite";
import { defineConfig, loadEnv, ConfigEnv, UserConfig } from "vite";
import { ElementPlusResolver } from "unplugin-vue-components/resolvers";
import AutoImport from "unplugin-auto-import/vite";
import eslint from "vite-plugin-eslint";
import path from "path";

export default defineConfig(({ mode }: ConfigEnv): UserConfig => {
  const root = process.cwd();
  const env = loadEnv(mode, root); // 环境变量对象
  const { VITE_APP_API_DOMAIN } = env;
  const proxyTargetUrl = VITE_APP_API_DOMAIN;

  return {
    plugins: [
      vue(),
      eslint(),
      AutoImport({
        resolvers: [ElementPlusResolver()],
      }),
      Components({
        resolvers: [ElementPlusResolver()],
      }),
    ],
    resolve: {
      //设置别名
      alias: {
        "@": path.resolve(__dirname, "src"),
      },
    },
    css: {
      preprocessorOptions: {
        less: {
          additionalData: "@import '@/style/variable.less';",
        },
      },
    },
    server: {
      port: 8010, //启动端口
      hmr: {
        host: "localhost",
        port: 8010,
      },
      // 设置 https 代理
      proxy: {
        "/api": {
          target: proxyTargetUrl,
          changeOrigin: true,
          // rewrite: (path: string) => path.replace(/^\/api/, 'api/')
        },
      },
    },
  };
});

1.6.3 vite.config.ts 参考配置

import { defineConfig, loadEnv } from "vite";
import type { UserConfig, ConfigEnv } from "vite";
import vue from "@vitejs/plugin-vue";
import vueJsx from "@vitejs/plugin-vue-jsx"; //tsx插件引入
import AutoImport from "unplugin-auto-import/vite"; //自动引入ref,reactive等等等
// 配置antd-v按需加载
import Components from "unplugin-vue-components/vite";
import { AntDesignVueResolver } from "unplugin-vue-components/resolvers";
// import path from 'path';
import { resolve, join } from "path";
import { wrapperEnv } from "./build/utils";

// defineConfig 工具函数,这样不用 jsdoc 注解也可以获取类型提示
export default defineConfig(({ command, mode }: ConfigEnv): UserConfig => {
  console.log(command, mode, "===");
  const root = process.cwd();
  const env = loadEnv(mode, root); // 环境变量对象
  console.log("环境变量------", env);
  console.log("文件路径( process.cwd())------", root);
  console.log("文件路径(dirname)------", __dirname + "/src");
  const { VITE_DROP_CONSOLE } = wrapperEnv(env);

  // // dev 独有配置
  return {
    root, //项目根目录(index.html 文件所在的位置) 默认: process.cwd()
    base: "/", //  开发或生产环境服务的公共基础路径:默认'/'   1、绝对 URL 路径名: /foo/;  2、完整的 URL: https://foo.com/; 3、空字符串或 ./(用于开发环境)
    publicDir: resolve(__dirname, "./dist"), //默认'public'  作为静态资源服务的文件夹  (打包public文件夹会没有,里面得东西会直接编译在dist文件下)
    assetsInclude: resolve(__dirname, "./src/assets"), // 静态资源处理

    // ******插件配置******
    plugins: [
      vue(),
      vueJsx(),
      AutoImport({
        imports: [
          "vue",
          "vue-router",
          "pinia",
          {
            axios: [
              ["default", "axios"], // import { default as axios } from 'axios',
            ],
          },
        ],
        dts: "types/auto-import.d.ts", //生成全局引入的文件
      }),
      Components({
        resolvers: [
          AntDesignVueResolver({
            importStyle: "less", //修改antdv主题色
          }),
        ],
      }),
    ], //配置插件
    // ******开发服务器配置******
    server: {
      https: true, //(使用https)启用 TLS + HTTP/2。注意:当 server.proxy 选项 也被使用时,将会仅使用 TLS
      host: true, // 监听所有地址
      port: 8080, //指定开发服务器端口:默认3000
      open: true, //启动时自动在浏览器中打开
      cors: false, //为开发服务器配置 CORS
      proxy: {
        //配置自定义代理规则
        // 字符串简写写法
        "/jpi": "http://192.168.1.97:4567",
        "/api": {
          target: "http://192.168.1.97:108",
          changeOrigin: true, //是否跨域
          rewrite: (path) => path.replace(/^\/api/, ""),
        },
      },
      // hmr: {
      //   overlay: false
      // }
    },
    // ******项目构建配置******
    build: {
      target: "modules", //设置最终构建的浏览器兼容目标  //es2015(编译成es5) | modules
      outDir: "dist", // 构建得包名  默认:dist
      assetsDir: "assets", // 静态资源得存放路径文件名  assets
      sourcemap: false, //构建后是否生成 source map 文件
      brotliSize: false, // 启用/禁用 brotli 压缩大小报告。 禁用该功能可能会提高大型项目的构建性能
      minify: "esbuild", // 项目压缩 :boolean | 'terser' | 'esbuild'
      chunkSizeWarningLimit: 1000, //chunk 大小警告的限制(以 kbs 为单位)默认:500
      cssTarget: "chrome61", //防止 vite 将 rgba() 颜色转化为 #RGBA 十六进制符号的形式  (要兼容的场景是安卓微信中的 webview 时,它不支持 CSS 中的 #RGBA 十六进制颜色符号)
    },
    // ******resolver配置******
    resolve: {
      alias: {
        // 别名配置
        // 键必须以斜线开始和结束
        "@": resolve(__dirname, "src"),
        components: resolve(__dirname, "./src/components"),
        assets: resolve(__dirname, "./src/assets"),
        "#": resolve(__dirname, "types"),
        build: resolve(__dirname, "build"),
      },
    },
    // ******打印+debugger清除配置******
    // 测试环境保留打印
    esbuild: {
      pure: VITE_DROP_CONSOLE ? ["console.log", "debugger"] : [],
    },

    css: {
      // 全局变量+全局引入less+配置antdv主题色
      preprocessorOptions: {
        less: {
          javascriptEnabled: true,
          // 全局变量使用:@primary-color
          modifyVars: {
            "primary-color": "#1890ff", // 全局主色
            "link-color": " #1890ff", // 链接色
            "success-color": " #52c41a", // 成功色
            "warning-color": " #faad14", // 警告色
            "error-color": " #f5222d", // 错误色
            "font-size-base": " 14px", // 主字号
            "heading-color": " rgba(0, 0, 0, 0.85)", // 标题色
            "text-color": " rgba(0, 0, 0, 0.65)", // 主文本色
            "text-color-secondary": " rgba(0, 0, 0, 0.45)", // 次文本色
            "disabled-color": " rgba(0, 0, 0, 0.25)", // 失效色
            "border-radius-base": " 2px", // 组件/浮层圆角
            "border-color-base": " #d9d9d9", // 边框色
            "box-shadow-base": " 0 2px 8px rgba(0, 0, 0, 0.15)", // 浮层阴影
          },
        },
      },
    },
  };
});
Contributors: masecho