vue3+vite+ts搭建

vue3+vite+ts搭建

vite初始化

使用 NPM:
npm init @vitejs/app
使用 Yarn:
yarn create @vitejs/app

选择模板

选择 vue-ts

或通过附加的命令行选项直接指定项目名和模板

# npm 6.x
npm init @vitejs/app vite-vue3-starter --template vue-ts

# npm 7+(需要额外的双横线)
npm init @vitejs/app vite-vue3-starter -- --template vue-ts

# yarn
yarn create @vitejs/app vite-vue3-starter --template vue-ts

安装依赖

npm install

启动项目

npm run dev

vite配置文件

vite.config.ts根目录下

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'
import Components from 'unplugin-vue-components/vite' //针对 Vue 的按需组件自动导入
import { AntDesignVueResolver } from 'unplugin-vue-components/resolvers'
import viteCompression from 'vite-plugin-compression'
import WindiCSS from 'vite-plugin-windicss'
import { createHtmlPlugin } from 'vite-plugin-html'
import AutoImport from 'unplugin-auto-import/vite'
import autoprefixer from 'autoprefixer'
// import viteStylelint from '@amatlash/vite-plugin-stylelint'
export default defineConfig({
  plugins: [
    vue(),
    createHtmlPlugin({
      minify: true,
      /**
       * 在这里写entry后,你将不需要在`index.html`内添加 script 标签,原有标签需要删除
       * @default src/main.ts
       */
      // entry: 'src/main.ts',
      /**
       * 如果你想将 `index.html`存放在指定文件夹,可以修改它,否则不需要配置
       * @default index.html
       */
      // template: './index.html',
      /**
       * 需要注入 index.html ejs 模版的数据
       */
      inject: {
        data: {
          title: 'SW网',
          injectScript: ``
        },
        tags: [
          {
            injectTo: 'body-prepend',
            tag: 'div',
            attrs: {
              id: 'app'
            }
          }
        ]
      }
    }),
    // viteStylelint({
    //   // 对某些文件排除检查
    //   exclude: /windicss|node_modules/
    // }),
    AutoImport({
      //自动导入vue3的hooks
      imports: [
        'vue',
        'vue-router',
        {
          axios: [
            ['default', 'axios'] // import { default as axios } from 'axios',
          ]
        }
      ],
      resolvers: [
        /* ... */
      ]
    }),
    WindiCSS(),
    Components({
      //针对 Vue 的按需组件自动导入
      dts: true, // ts支持
      dirs: ['src/components', 'src/views'], // 自定义路径按需导入
      resolvers: [AntDesignVueResolver()] // antd直接使用组件,无需在任何地方导入组件
    }),
    viteCompression({
      verbose: true, //是否在控制台输出压缩结果
      disable: false, //是否禁用
      threshold: 10240, //体积大于 threshold 才会被压缩,单位 b
      algorithm: 'gzip', //压缩算法,可选 [ 'gzip' , 'brotliCompress' ,'deflate' , 'deflateRaw']
      ext: '.gz' //生成的压缩包后缀
    })
  ],

  resolve: {
    //extensions: ['.vue', '.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.node', '.scss'],
    alias: {
      '@': resolve('./src'),
      '@vi': resolve('./src/views'),
      '@api': resolve('./src/api'),
      '@h': resolve('./src/hooks'),
      '@page': resolve('./src/views/page'),
      '@comp': resolve('./src/components'),
      '@assets': resolve('./src/assets')
    }
  },
  base: '/', // 打包路径
  server: {
    port: 4000, // 服务端口号
    open: false, // 服务启动时是否自动打开浏览器
    cors: true, // 允许跨域
    fs: {
      strict: false
    }
  },
  css: {
    // 进行 PostCSS 配置
    postcss: {
      plugins: [
        autoprefixer({
          // 指定目标浏览器
          overrideBrowserslist: ['Chrome > 40', 'ff > 31', 'ie 11']
        })
      ]
    },
    preprocessorOptions: {
      // less: {},
      scss: {
        // 避免出现: build时的 @charset 必须在第一行的警告
        charset: false,
        additionalData: '@import "./src/design/com.scss";'
      }
    }
  },

  build: {
    // 打包配置
    // minify: 'terser', //esbuild  terser
    assetsInlineLimit: 8 * 1024, //如果静态资源体积 >= 4KB,则提取成单独的文件 如果静态资源体积 < 4KB,则作为 base64 格式的字符串内联
    terserOptions: {
      compress: {
        // 打包后移除console和注释
        drop_console: true,
        drop_debugger: true
      }
    },
    // 拆分打包的配置方法
    assetsDir: 'public/static',
    rollupOptions: {
      output: {
        chunkFileNames: 'static/js/[name]-[hash].js',
        entryFileNames: 'static/js/[name]-[hash].js',
        assetFileNames: 'static/[ext]/[name]-[hash].[ext]',
        manualChunks: {
          // 拆分代码,这个就是分包,配置完后自动按需加载
          vue: ['vue', 'vue-router']
        }
      }
    }
  }
})

集成路由

安装支持 Vue3 的路由工具 vue-router@4

npm i vue-router@4

创建 src/router/index.ts 文件
在 src 下创建 router 目录,然后在 router 目录里新建 index.ts 文件:

 └── src/
     ├── router/
         ├── index.ts  // 路由配置文件
import { createRouter, createWebHistory, RouteRecordRaw, _RouteRecordBase } from 'vue-router'

declare module 'vue-router' {
  interface _RouteRecordBase {
    hidden?: boolean | string | number
  }
}
const routes: RouteRecordRaw[] = [
  {
    path: '/',
    name: 'Home',
    component: Home
  },
  {
    path: '/vuex',
    name: 'Vuex',
    component: Vuex
  },
  {
    path: '/axios',
    name: 'Axios',
    component: () => import('@/views/axios.vue') // 懒加载组件
  }
]

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

export default router

main.ts 文件中挂载路由配置

import { createApp } from 'vue'
import App from './App.vue'

import router from './router/index'
// use
const app = createApp(App)
app.use(router)

集成状态管理工具

安装支持 Vue3 的状态管理工具 vuex@next

npm i vuex@next

创建 src/store/index.ts 文件

src 下创建 store 目录,然后在 store 目录里新建 index.ts 文件:

import { createStore } from 'vuex'
const defaultState = {
  count: 0
}
// Create a new store instance.
export default createStore({
  state() {
    return defaultState
  },
  mutations: {
    increment(state: typeof defaultState) {
      state.count++
    }
  },
  actions: {
    increment(context) {
      context.commit('increment')
    }
  },
  getters: {
    double(state: typeof defaultState) {
      return 2 * state.count
    }
  }
})

main.ts 文件中挂载 Vuex 配置

import { createApp } from 'vue'
import App from './App.vue'
import store from './store/index'
createApp(App).use(store).mount('#app')

集成 Axios

npm i axios
import axios from 'axios'
import qs from 'qs'
import store from '@/store/index'
import router from '@/router/index'
import { aspShow } from '@/hooks/data'
import { message } from 'ant-design-vue/es'
import NProgress from 'nprogress'

console.log(import.meta.env.VITE_API_DOMAIN)
axios.defaults.baseURL = import.meta.env.VITE_API_DOMAIN
axios.defaults.timeout = 60000
axios.defaults.headers.post['Content-Type'] = 'application/json;charset=UTF-8'
axios.defaults.headers.post['Access-Control-Allow-Origin-Type'] = '*'
axios.interceptors.request.use(
  function (config: any) {
    aspShow.value = true
    NProgress.start()
    // 在发送请求之前做某件事
    if (config.method === 'post' || config.method === 'put' || config.method === 'delete') {
      if (config.method !== 'post' && config.headers['Content-Type'] !== 'multipart/form-data') {
        config.data = qs.parse(config.data)
      }
    }
    // 若是有做鉴权token , 就给头部带上token
    if (store.state.token) {
      config.headers.Authorization = store.state.token
    }
    return config
  },
  (error: { data: { error: { message: any } } }) => {
    return Promise.reject(error.data.error.message)
  }
)

axios.interceptors.response.use(
  function (config: any) {
    aspShow.value = false
    NProgress.done()
    if (config.status === 200 || config.status === 204) {
      return Promise.resolve(config)
    }
    return Promise.reject(config)
  },
  function (error: any) {
    aspShow.value = false
    NProgress.done()
    if (error.response.status) {
      switch (error.response.status) {
        // 401: 未登录
        // 未登录则跳转登录页面,并携带当前页面的路径
        // 在登录成功后返回当前页面,这一步需要在登录页操作。
        case 401:
          router.replace({
            path: '/login',
            query: {}
          })
          break
        // 403 token过期
        case 403:
          // Toast({
          //   message: '登录过期,请重新登录',
          //   duration: 1000,
          //   forbidClick: true
          // });
          // 清除token
          store.dispatch('FedLogOut').then(() => {
            // 跳转登录页面,并将要浏览的页面fullPath传过去,登录成功后跳转需要访问的页面
            router.replace({
              path: '/login',
              query: {
                // redirect: router.currentRoute.fullPath
              }
            })
          })
          break

        // 404请求不存在
        case 404:
          // Toast({
          //   message: '网络请求不存在',
          //   duration: 1500,
          //   forbidClick: true
          // });
          break
        // 其他错误,直接抛出错误提示
        default:
          message.error(`${error.response.status}:${error.response.statusText}`)
      }
      aspShow.value = false
      NProgress.done()
      return Promise.reject(error)
    }
    // 处理断网的情况
    // eg:请求超时或断网时,更新state的network状态
    // network状态在app.vue中控制着一个全局的断网提示组件的显示隐藏
    // 关于断网组件中的刷新重新获取数据,会在断网组件中说明
    store.commit('changeNetwork', false)
  }
)
export default axios

集成 CSS 预编译器

npm i stylus -D
# or
npm i sass -D
npm i less -D
css: {
    preprocessorOptions: {
      less: {
        javascriptEnabled: true
      },
      scss: {
        // 避免出现: build时的 @charset 必须在第一行的警告
        charset: false,
        additionalData: '@import "./src/design/methodCss.scss";'
      }
    }
  }

代码规范

使用 EditorConfig + Prettier + ESLint 组合来实现代码规范化

这样做带来好处:

  • 解决团队之间代码不规范导致的可读性差和可维护性差的问题。
  • 解决团队成员不同编辑器导致的编码规范不统一问题。
  • 提前发现代码风格问题,给出对应规范提示,及时修复。
  • 减少代码审查过程中反反复复的修改过程,节约时间。
  • 自动格式化,统一编码风格,从此和脏乱差的代码说再见。

集成 EditorConfig 配置

在项目根目录下增加 .editorconfig 文件:

# Editor configuration, see http://editorconfig.org

# 表示是最顶层的 EditorConfig 配置文件
root = true

[*] # 表示所有文件适用
charset = utf-8 # 设置文件字符集为 utf-8
indent_style = space # 缩进风格(tab | space)
indent_size = 2 # 缩进大小
end_of_line = lf # 控制换行类型(lf | cr | crlf)
trim_trailing_whitespace = true # 去除行首的任意空白字符
insert_final_newline = true # 始终在文件末尾插入一个新行

[*.md] # 表示仅 md 文件适用以下规则
max_line_length = off
trim_trailing_whitespace = false
复制代码

注意:

  • VSCode 使用 EditorConfig 需要去插件市场下载插件 EditorConfig for VS Code

集成 Prettier 配置

Prettier 是一款强大的代码格式化工具,支持 JavaScript、TypeScript、CSS、SCSS、Less、JSX、Angular、Vue、GraphQL、JSON、Markdown 等语言,基本上前端能用到的文件格式它都可以搞定,是当下最流行的代码格式化工具。

  1. 安装 Prettier

    npm i prettier -D
    pnpm add prettier -D
    
  2. 创建 Prettier 配置文件

    Prettier 支持多种格式的配置文件,比如 .json.yml.yaml.js等。

    在本项目根目录下创建 .prettierrc 文件。

  3. 配置 .prettierrc

    在本项目中,我们进行如下简单配置,关于更多配置项信息,请前往官网查看 Prettier-Options

    module.exports = {
      printWidth: 120, //单行长度,超过则自动换行
      // 设置tab宽度为2个空格
      tabWidth: 2,
      // 语句末尾要加分号
      semi: false,
      useTabs: false,
      // 使用单引号
      singleQuote: true,
      // (x) => {} 箭头函数参数只有一个时是否要有小括号。avoid:省略括号 always始终包含
      arrowParens: 'avoid',
      // 在对象,数组括号与文字之间加空格 "{ foo: bar }"
      bracketSpacing: true,
      endOfLine: 'auto', //避免报错delete (cr)的错 维护现有的行尾(通过查看第一行之后使用的内容,对一个文件中的混合值进行规范化)
      trailingComma: 'none', // 是否使用尾逗号,
      embeddedLanguageFormatting: 'off', //"auto"– 格式化嵌入式代码,如果 Prettier 可以自动识别它. off"- 从不自动格式化嵌入式代码。
      htmlWhitespaceSensitivity: 'ignore',
      // 18. vue script和style标签中是否缩进,开启可能会破坏编辑器的代码折叠
      vueIndentScriptAndStyle: false,
      // 20.embeddedLanguageFormatting: "off",默认是auto,控制被引号包裹的代码是否进行格式化
      embeddedLanguageFormatting: 'off',
      bracketSameLine: true //将多行 HTML(HTML、JSX、Vue、Angular)元素放在最后一行的末尾,而不是单独放在下一行(不适用于自闭合元素)
    }
    
  4. Prettier 安装且配置好之后,就能使用命令来格式化代码

    # 格式化所有文件(. 表示所有文件)
    npx prettier --write .
    

注意:

  • VSCode 编辑器使用 Prettier 配置需要下载插件 Prettier - Code formatter

package.json中添加命令

{
    "scripts": {
        "format": "prettier --write \"./**/*.{html,vue,ts,js,json,md}\"",
    }
}

运行该命令,会将我们项目中的文件都格式化一遍,**如果添加其他格式的文件,可在该命令中添加,例如:.less后缀的文件

集成 ESLint 配置

ESLint 是一款用于查找并报告代码中问题的工具,并且支持部分问题自动修复。其核心是通过对代码解析得到的 AST(Abstract Syntax Tree 抽象语法树)进行模式匹配,来分析代码达到检查代码质量和风格问题的能力。

正如前面我们提到的因团队成员之间编程能力和编码习惯不同所造成的代码质量问题,我们使用 ESLint 来解决,一边写代码一边查找问题,如果发现错误,就给出规则提示,并且自动修复,长期下去,可以促使团队成员往同一种编码风格靠拢。

安装 ESLint

可以全局或者本地安装,作者推荐本地安装(只在当前项目中安装)。

npm i eslint -D

配置 ESLint

ESLint 安装成功后,执行 npx eslint --init,然后按照终端操作提示完成一系列设置来创建配置文件。

  • How would you like to use ESLint? (你想如何使用 ESLint?)

    选择 To check syntax, find problems, and enforce code style(检查语法、发现问题并强制执行代码风格)

  • What type of modules does your project use?(你的项目使用哪种类型的模块?)

    选择 JavaScript modules (import/export)

  • Which framework does your project use? (你的项目使用哪种框架?)

    选择 Vue.js

  • Does your project use TypeScript?(你的项目是否使用 TypeScript?)

    选择 Yes

  • Where does your code run?(你的代码在哪里运行?)

    选择 Browser 和 Node(按空格键进行选择,选完按回车键确定)

  • How would you like to define a style for your project?(你想怎样为你的项目定义风格?)

    选择 Use a popular style guide(使用一种流行的风格指南)

  • Which style guide do you want to follow?(你想遵循哪一种风格指南?)

    选择 Airbnb: github.com/airbnb/java…

    ESLint 为我们列出了三种社区流行的 JavaScript 风格指南,分别是 Airbnb、Standard、Google。

    这里作者不建议大家去自由配置 ESLint 规则,相信我,这三份 JavaScript 代码风格指南值得我们反复学习,掌握后,编程能力能上一大台阶。

  • What format do you want your config file to be in?(你希望你的配置文件是什么格式?)

    选择 JavaScript

  • Would you like to install them now with npm?(你想现在就用 NPM 安装它们吗?)

    ESLint 会自动去查找缺失的依赖,我们这里选择 Yes,使用 NPM 下载安装这些依赖包。

    注意:如果自动安装依赖失败,那么需要手动安装

    npm i @typescript-eslint/eslint-plugin @typescript-eslint/parser eslint-config-airbnb-base eslint-plugin-import eslint-plugin-vue -D
    

ESLint 配置文件 .eslintrc.js

会在项目根目录下自动生成 .eslintrc.js 配置文件:

module.exports = {
  env: {
    browser: true,
    es2021: true
  },
  extends: ['plugin:vue/essential', 'airbnb-base', 'plugin:prettier/recommended'],
  parserOptions: {
    ecmaVersion: 13,
    parser: '@typescript-eslint/parser',
    sourceType: 'module'
  },
  plugins: ['vue', '@typescript-eslint'],
  rules: {
    'no-console': 'off',
    'import/no-unresolved': 'off',
    'import/extensions': 'off',
    'import/no-absolute-path': 'off',
    'import/no-extraneous-dependencies': 'off',
    'vue/no-v-model-argument': 'off'
  },
  globals: {
    defineProps: true,
    defineEmits: true
  }
}

根据项目实际情况,如果我们有额外的 ESLint 规则,也在此文件中追加。

注意:

  • VSCode 使用 ESLint 配置文件需要去插件市场下载插件 ESLint

虽然,现在编辑器已经给出错误提示和修复方案,但需要我们一个一个去点击修复,还是挺麻烦的。很简单,我们只需设置编辑器保存文件时自动执行 eslint --fix 命令进行代码风格修复。

  • VSCode 在 settings.json 设置文件中,增加以下代码:

     "editor.codeActionsOnSave": {
        "source.fixAll.eslint": true
     }
    

解决 Prettier 和 ESLint 的冲突

通常大家会在项目中根据实际情况添加一些额外的 ESLint 和 Prettier 配置规则,难免会存在规则冲突情况。

本项目中的 ESLint 配置中使用了 Airbnb JavaScript 风格指南校验,其规则之一是代码结束后面要加分号,而我们在 Prettier 配置文件中加了代码结束后面不加分号的配置项,这样就有冲突了,会出现用 Prettier 格式化后的代码,ESLint 检测到格式有问题的,从而抛出错误提示。

解决两者冲突问题,需要用到 eslint-plugin-prettiereslint-config-prettier

  • eslint-plugin-prettier 将 Prettier 的规则设置到 ESLint 的规则中。
  • eslint-config-prettier 关闭 ESLint 中与 Prettier 中会发生冲突的规则。

最后形成优先级:Prettier 配置规则 > ESLint 配置规则

  • 安装插件

    npm i eslint-plugin-prettier eslint-config-prettier -D
    
  • .eslintrc.js 添加 prettier 插件

    module.exports = {
      ...
      extends: [
        'plugin:vue/essential',
        'airbnb-base',
        'plugin:prettier/recommended' // 添加 prettier 插件
      ],
      ...
    }
    

这样,我们在执行 eslint --fix 命令时,ESLint 就会按照 Prettier 的配置规则来格式化代码,轻松解决二者冲突问题。

posted @ 2021-11-30 14:47  少年。  阅读(2216)  评论(2编辑  收藏  举报