Vite 开发实践 - 项目搭建

3,462 阅读13分钟

前言

在公司试着用vite搭建了项目,总体来说收获与坑并存吧。一方面vite确实给了我极好的开发体验,另一方面由于它本身出现的时间比较晚,社区生态并不是很完善,所以很多坑还是要自己去踩。不过幸运的是最后都找到了解决办法,后续也准备写几篇文章来细说一下vite里面的一些坑和实践,下面是第一篇文章,vite的相关环境搭建部分。

什么是 vite

vite是一个新型的开发构建工具,开发环境使用esbuild预构建依赖过程,生产环境中使用rollup作为打包工具以适配更好的性能,并以 原生 ESM 方式提供源码。

也就是说,让浏览器接管了打包程序的部分工作,vite只需要在浏览器请求源码时进行转换并按需提供源码。根据情景动态导入代码,即只在当前屏幕上实际使用时才会被处理

附一张官方的图:

image.png

它的出现主要是为了解决以下现实问题:

  • 缓慢的服务器启动:以前基于打包器的方式的构建工具(比如 webpack)冷启动开发服务器时,启动必须优先抓取并构建整个应用才能提供服务,当项目越来越大时会非常的耗时。
  • 缓慢的更新:基于打包器启动时,重建整个包的效率很低,原因同上。

初始化 vite 项目

由于作者的技术栈是React,并且项目多以typescript为主,所以本文的示例暂且都以React + TS的方式组织,一些需要额外说明的地方会针对不同框架进行说明。

npm init vite@latest
# or 
yarn create vite

image.png

初始化完成后,可以cd进目录,项目的结构很简单:

image.png

创建完项目后第一步看下package.json文件:

{
  "name": "vite-todo",
  "version": "0.0.0",
  "scripts": {
    "dev": "vite",
    "build": "tsc && vite build",
    "serve": "vite preview"
  },
  "dependencies": {
    "react": "^17.0.0",
    "react-dom": "^17.0.0"
  },
  "devDependencies": {
    "@types/react": "^17.0.0",
    "@types/react-dom": "^17.0.0",
    "@vitejs/plugin-react-refresh": "^1.3.1",
    "typescript": "^4.3.2",
    "vite": "^2.4.4"
  }
}

可以看到,vite本身将启动命令单独抽离出来了,运行npm run dev即可进入开发模式。

继续看项目目录,根目录下有一个vite.config.ts文件,该文件是vite的默认配置文件,vite启动时默认会读取它内部的相关配置。并且还有一个index.html文件,该文件官方也有解释,直接看这里就行了。

ok,基本的使用方式我们知道了,不过官方给的目标默认的初始化信息太少了,代码格式和eslint配置等都没有,下面我们就来丰富一些官方的启动模板。

项目配置

项目配置其实大体同webpack的工程化配置,一般就是加入各种插件就行了,并不会有太多不同的写法,目前我这里写的都是vite文档中介绍比较少或者没有介绍的地方,其余包括开发服务的 proxy,css 预处理器的支持推荐看对应的官方文档就行了。

添加项目别名

不管你是ts还是js项目,我这边建议项目别名都添加在两个地方,vite.config.tstsconfig.json(或vite.config.jsjsconfig.json)。

前一个配置是为了vite解析时能识别别名,这是必须配置的。后一个配置是为了你的编译器能够识别别名,在我看来这也是不可或缺的,它能给我们带来非常好的开发体验。

具体配置如下:

注意: 如果你使用的也是 typescript,需要先npm install @types/node -D下载Node API的相关类型提示。

  • vite.config.ts

    import { defineConfig } from 'vite'
    import path from 'path'
    import reactRefresh from '@vitejs/plugin-react-refresh'
    
    function resolve(relativePath: string) {
      return path.resolve(__dirname, relativePath)
    }
    
    // https://vitejs.dev/config/
    export default defineConfig({
      plugins: [reactRefresh()],
      resolve: {
        alias: {
          '@': resolve('./src')
        }
      }
    })
    
  • tsconfig.json(或 jsconfig.json)

    添加下面这段代码:

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

现在,我们的项目中就可以完美地支持别名了。

修改 tsconfig.json

vite 原本的tsconfig.json打包时可能会报错,并且在本项目中的文件匹配可能也会出错,所以这边简单改一下:

{
  "compilerOptions": {
    "target": "ESNext",
    "lib": ["DOM", "DOM.Iterable", "ESNext"],
    "allowJs": true,
    "checkJs": true,
    // 填过 lib 检查
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "module": "ESNext",
    "moduleResolution": "Node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react",
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"]
    }
  },
  // 只手动排除不检测的目录
  "exclude": ["node_modules", "dist", "public"]
}

添加 eslint 检查 js 代码

这里所有配置文件我都建议能用js书写就用js书写,可以很方便地进行代码扩展。

  • 下载eslint相关配置:
    • 添加prettier风格
      # 我这边使用的是 prettier 风格,具体可自行选择
      npm install eslint eslint-plugin-prettier eslint-config-prettier -D
      
    • 添加typescript相关 lint
      npm install @typescript-eslint/parser @typescript-eslint/eslint-plugin -D
      
    • 添加ES6模块导入相关 lint
      npm install eslint-plugin-import eslint-import-resolver-typescript -D
      
      eslint-import-resolver-typescript用于识别ts的中的模块。
    • 添加React相关 lint
      npm install eslint-plugin-react eslint-plugin-react-hooks eslint-plugin-jsx-a11y -D
      
    • 一些其他的插件(可选):如果要用可以自己去查看文档进行配置。
      • eslint-plugin-eslint-comments用于指令注释eslint规则应用的最佳实践。
      • eslint-plugin-jest用于使用jest的特定 lint
  • 项目中新增.eslintrc.js文件,相关配置如下:
    const __DEV__ = process.env.NODE_ENV !== 'production'
    
    module.exports = {
      env: {
        browser: true,
        es2021: true,
        node: true,
      },
      root: true,
      extends: [
        'eslint:recommended',
        'plugin:eslint-comments/recommended',
        'plugin:import/recommended',
        // ts 支持
        'plugin:import/typescript',
        'plugin:react/recommended',
        'plugin:react-hooks/recommended',
        'plugin:jsx-a11y/recommended',
        // plugin:prettier/recommended 需要为最后一个扩展
        'plugin:prettier/recommended',
      ],
      // rules 可根据条件自行配置
      rules: {
        // prettier
        'prettier/prettier': 'warn',
        // js
        // 'import/default': 'off',
        'no-shadow': 'error',
        'no-unused-vars': 'warn',
        'no-debugger': __DEV__ ? 'off' : 'warn', // 调试
        'no-console': __DEV__ ? 'off' : 'warn', // 日志打印
        'require-yield': 'warn', // 不允许 generate 函数中没有 yield
        'import/no-named-as-default': 'off',
        'import/no-named-as-default-member': 'off',
        // react
        'react/self-closing-comp': 'error',
        // click element muse have keyboard events
        'jsx-a11y/click-events-have-key-events': 'off',
        // click element must have a role property
        'jsx-a11y/no-static-element-interactions': 'off',
        // comments,下面安装了 eslint-plugin-eslint-comments 才配置
        'eslint-comments/disable-enable-pair': [
          'warn',
          {
            allowWholeFile: true,
          },
        ],
      },
      settings: {
        react: {
          version: 'detect',
        },
        'import/parsers': {
          '@typescript-eslint/parser': ['.ts', '.tsx'],
        },
        'import/extensions': ['.tsx', '.ts', '.js', '.jsx', '.json'],
        'import/resolver': {
          typescript: {
            project: ['./jsconfig.json', './tsconfig.json'],
          },
        },
      },
      // ts 规则单独覆盖
      overrides: [
        {
          files: ['*.ts', '*.tsx'],
          // 只针对 ts 用 typescript-eslint
          parser: '@typescript-eslint/parser',
          // 开启静态检查
          parserOptions: {
            tsconfigRootDir: __dirname,
            ecmaFeatures: {
              jsx: true,
            },
            project: ['./tsconfig.json'],
          },
          plugins: ['@typescript-eslint'],
          extends: [
            'plugin:@typescript-eslint/recommended',
            'plugin:@typescript-eslint/recommended-requiring-type-checking',
          ],
          rules: {
            // close js rules
            'no-shadow': 'off',
            // ts
            '@typescript-eslint/no-var-requires': 'warn',
            '@typescript-eslint/no-shadow': 'error',
            '@typescript-eslint/explicit-module-boundary-types': 'off',
            '@typescript-eslint/no-unsafe-member-access': 'off',
            // no any
            '@typescript-eslint/no-explicit-any': 'off',
            '@typescript-eslint/no-unsafe-assignment': 'off',
            '@typescript-eslint/no-unsafe-return': 'off',
            '@typescript-eslint/no-unsafe-call': 'off',
            // ! operator
            '@typescript-eslint/no-non-null-assertion': 'off',
          },
        },
      ],
    }
    
  • 项目中添加忽略文件.eslintignore:
    # 忽略 node_modules、打包目录和公共资源目录
    node_modules
    dist
    public
    
  • 下载vscodeeslint插件
  • 下载vite运行时插件:
    npm install vite-plugin-eslint -D
    
    加入vite.config.ts中:
    import { defineConfig } from 'vite'
    import path from 'path'
    import eslintPlugin from 'vite-plugin-eslint'
    import reactRefresh from '@vitejs/plugin-react-refresh'
    
    function resolve(relativePath: string) {
      return path.resolve(__dirname, relativePath)
    }
    
    // https://vitejs.dev/config/
    export default defineConfig({
      plugins: [
        reactRefresh(),
        eslintPlugin({
          fix: true,
          include: ['./src/**/*.[tj]s?(x)'],
        }),
      ],
      resolve: {
        alias: {
          '@': resolve('./src'),
        },
      },
    })
    

添加 stylelint 检查 css 代码

  • 下载stylelint相关配置:
    • 添加官方推荐配置:
      npm install stylelint stylelint-config-standard -D
      
    • 添加prettier风格
      npm install stylelint-prettier stylelint-config-prettier -D
      
    • 添加用于排序的规则:
      npm install stylelint-order stylelint-config-rational-order -D
      
    • 添加提醒样式冲突的规则:
      npm install stylelint-declaration-block-no-ignored-properties -D
      
    • 一些其他插件(可选):
      • stylelint-scss:用于scss文件的规则校验
      • stylelint-less:用于less文件的规则校验
  • 项目中新增stylelint.config.js文件:
    module.exports = {
      extends: [
        // 标准配置
        'stylelint-config-standard',
        // 用于排序
        'stylelint-config-rational-order',
        // 放在最后
        'stylelint-prettier/recommended',
      ],
      plugins: [
        // 提示书写矛盾的样式
        'stylelint-declaration-block-no-ignored-properties',
      ],
      rules: {
        'plugin/declaration-block-no-ignored-properties': true,
        'prettier/prettier': true,
        'rule-empty-line-before': [
          'always',
          {
            // 防止和 prettier 冲突
            except: ['first-nested'],
          },
        ],
        'selector-pseudo-class-no-unknown': [
          true,
          {
            ignorePseudoClasses: ['global'],
          },
        ],
      },
      // stylelint 支持直接配置忽略文件
      ignoreFiles: ['node_modules/**/*', 'dist/**/*', 'public/**/*'],
    }
    
  • 下载vscodeprettier插件
  • 下载vite运行时插件:
    npm install @amatlash/vite-plugin-stylelint -D
    
    加入vite.config.ts中:
    import { defineConfig } from 'vite'
    import path from 'path'
    import eslintPlugin from 'vite-plugin-eslint'
    import viteStylelint from '@amatlash/vite-plugin-stylelint'
    import reactRefresh from '@vitejs/plugin-react-refresh'
    
    function resolve(relativePath: string) {
      return path.resolve(__dirname, relativePath)
    }
    
    // https://vitejs.dev/config/
    export default defineConfig({
      plugins: [
        reactRefresh(),
        eslintPlugin({
          fix: true,
          include: ['./src/**/*.[tj]s?(x)'],
        }),
        viteStylelint({
          include: './src/**/*.(less|scss|css)',
        }),
      ],
      resolve: {
        alias: {
          '@': resolve('./src'),
        },
      },
    })
    

添加 prettier 格式化代码样式

  • 下载prettier
    npm install prettier -D
    
  • 项目中新增prettier.config.js文件:
    module.exports = {
      printWidth: 80,
      tabWidth: 2,
      useTabs: false,
      semi: false,
      singleQuote: true,
      quoteProps: 'as-needed',
      jsxSingleQuote: false,
      trailingComma: 'es5',
      bracketSpacing: true,
      bracketSameLine: false,
      arrowParens: 'always',
      htmlWhitespaceSensitivity: 'ignore',
      vueIndentScriptAndStyle: true,
      endOfLine: 'lf',
    }
    
  • 项目中添加忽略文件.prettierignore:
    node_modules
    dist
    public
    
  • 下载vscodeprettier插件

解决 eslint、stylelint 与 prettier 的冲突

因为我们的eslintstylelint都是使用的prettier格式的 lint,一般来说都是可以识别项目中的prettier配置的,如果识别不了,一个简单的方法是直接在eslintstylelintprettier/prettier中手动同步prettier中的配置(这也是我为什么推荐使用js书写命名文件的原因)。

.eslintrc.jsstylelint.config.js中添加下面这句即可:

const prettierConfig = require('./prettier.config')

对应引入:

  • .eslintrc.js
    // eslint-disable-next-line @typescript-eslint/no-var-requires
    const prettierConfig = require('./prettier.config')
    
    module.exports = {
        // ...
        rules: {
            // ...
            'prettier/prettier': ['warn', prettierConfig]
            // ...
        }
        // ...
    }
    
  • stylelint.config.js
    const prettierConfig = require('./prettier.config')
    
    module.exports = {
        // ...
        rules: {
            // ...
            'prettier/prettier': [true, prettierConfig]
            // ...
        }
        // ...
    }
    

最后,别忘了添加手动格式化文件的命令:

{
    "scripts": {
     "lint": "npm run lint:eslint && npm run lint:stylelint && npm run lint:prettier",
     "lint:prettier": "prettier --write \"**/*.{ts,tsx,js,json,html,yml,css,less,scss,md}\"",
     "lint:eslint": "eslint --fix -c .eslintrc.js --ext .ts,.tsx,.js,.jsx .",
     "lint:stylelint": "stylelint --fix --config stylelint.config.js **/*.{less,css,scss,stylus}"
    }
}

运行npm run lint就可以自动修复所有指定文件了。

添加工作区配置统一格式化风格

为了统一格式化风格,我们可以直接在项目中添加工作区配置。在根目录下新建一个.vscode目录,在里面创建一个settings.json文件,配置如下:

{
  // 使用工作区的 typescript 版本
  "typescript.tsdk": "node_modules/typescript/lib",
 // svg 当做 html 解析
  "files.associations": {
    "*.svg": "html",
    // 识别所有 rc 配置文件
    "*rc": "json"
  },
  // 保存时 eslint 自动 fix
  "editor.codeActionsOnSave": {
    "source.fixAll": true
  },
  // 保存自动格式化
  "editor.formatOnSave": true,
  // 文件保存最后空一行
  "files.insertFinalNewline": true,
  // eslint 校验文件
  "eslint.validate": [
    "javascript",
    "javascriptreact",
    "typescript",
    "typescriptreact"
  ],
  // 配置默认的格式化工具
  "[html]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[json]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[vue]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[typescript]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[typescriptreact]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[javascript]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
   "[javascriptreact]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[jsonc]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  }
}

添加 commitlint 检验 git 提交信息

git 的提交信息校验也是项目中比较重要的一环,一般比较正规的提交信息格式如下:

<type>(<scope>): <subject>
# 这里空了一行
(<body>)
# 这里空了一行
(<footer>)

其中scopebodyfooter都是可选的,比如:

git commit -m "feat: init"
# or
git commit -m "feat(project): init"

规范了信息之后,我们还可以 commit 的内容生成更新日志,非常的方便。下面就是通过 git commit message 自动生成的日志: image.png

所以我们需要在每次提交的时候将不符合要求的提交信息拦截,保留正确格式的提交信息。我这边使用commitlint配合husky进行 git 提交校验。

npm install @commitlint/cli @commitlint/config-angular  -D # angular的提交格式

然后在项目中新建一个commitlint.config.js文件,添加相应规则:

/**
 * build : 改变了 build 工具 如 webpack
 * ci : 持续集成新增
 * chore : 构建过程或辅助工具的变动
 * feat : 新功能
 * docs : 文档改变
 * fix : 修复bug
 * perf : 性能优化
 * refactor : 某个已有功能重构
 * revert : 撤销上一次的 commit
 * style : 代码格式改变
 * test : 增加测试
 * anno: 增加注释
 */
module.exports = {
  // 扩展 angular 的提交配置
  extends: ['@commitlint/config-angular'],
  // 添加自定义规则
  rules: {
    'type-enum': [
      2,
      'always',
      [
        'build',
        'ci',
        'chore',
        'docs',
        'feat',
        'fix',
        'perf',
        'refactor',
        'revert',
        'style',
        'test',
        'anno',
      ],
    ],
  },
}

最后在package.json中添加 git hooks 执行的脚本(可以省略这一步,直接在 git hooks 里执行,但是为了拓展方便还是直接暴露出来吧)

{
    "scripts": {
        "commit-msg:commitlint": "commitlint --config commitlint.config.js -e $HUSKY_GIT_PARAMS",
    }
}

因为不是为了手动执行,所以尽量语义化一点。

使用 commitizen 代替 git commit 提交信息

在上面我们其实只是对提交信息做了约束,让用户必须手动提交符合要求的 commit message,事实上我们还可以依靠可视化的操作自动生成对应的提交信息,并且附加一些额外信息时也会更方便(比如 breaking changes、issue 的相关信息)。

我这边使用commitizen替代git commit提交信息,commitizen主要有两种使用方式:

  • 全局使用
    npm install commitizen -g
    
    然后使用git cz代替git commit执行git命令
  • 局部使用
    npm install commitizen -D
    
    然后使用npx cz执行git命令,当然也可以直接写在package.jsonscripts脚本里面:
    {
      "scripts": {
          "commit": "cz"
      }
    }
    

当然,还是加入angular的提交格式(全局和局部使用都是安装在本项目中):

npm install cz-conventional-changelog -D

在项目中新建一个.czrc文件:

{
  "path": "cz-conventional-changelog"
}

然后就可以愉快的使用了。

image.png

生成提交日志

最后,如果我们还想要生成git的提交日志,也可以下载对应的工具,我这里使用conventional-changelog-cli

npm install conventional-changelog-cli -D

package.json中加入一行命令:

{
    "scripts": {
        "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s"
    }
}

ok,现在我们运行npm run changelog就能生成对应日志了。

添加 lint-staged 检验代码质量

lint-staged一般都需要配合其他工具使用,如果你跟着我们的步骤走,那么现在配套工具也已经准备好了。

npm install lint-staged -D

然后在项目中新建一个.lintstagedrc文件(也可以写在package.json中,但是我个人比较喜欢把配置单独分文件管理),加入下面的代码:

{
  "*.{js,jsx,ts,tsx,css,scss,less,html,md,json}": [
    "npm run lint"
  ]
}

最后在package.json中加入一行script

{
    "scripts": {
        "pre-commit:lint-staged": "lint-staged",
    }
}

很明显,lint-staged我们也不是为了手动运行,它的检验我们一般当做是在其他 action 的附带行为,我在这里也主要是配合 git hooks 操作(当然我们其实也是能手动运行的)。

添加 husky 提供 git hooks 服务

git hooks 同样也是工程化项目中不可缺少的一环,它可以帮助我们在代码提交时做一系列排错操作,并且还可以检验我们提交的规范性。

在前端项目中我们一般使用husky来提供 git hooks 服务:

  • 下载husky
    npm install husky -D
    
  • 启用git钩子
    npx husky install
    
  • 在安装依赖后自动启用 git hooks:
    // package.json 
    { 
        "scripts": { 
            "prepare": "husky install" 
        } 
    }
    

执行完毕后,我们可以看到项目中会多出一个.husky目录,现在里面还没有任何钩子,现在我们来把我们需要的两个钩子加进去:

  • 添加pre-commit钩子:
    npx husky add .husky/pre-commit "npm run pre-commit:lint-staged"
    
  • 添加commit-msg钩子
    npx husky add .husky/pre-commit "npm run commit-msg:commitlint"
    

现在就可以享受到 git hooks 了 image.png

总结

本文为 Vite 开发实践 的第一篇文章,从初始化项目开始完整的搭建了一个配置详细的工程化项目,目前本文的相关配置也已经发布到了github的相关启动模板上,感兴趣的小伙伴可以直接使用。

注意: 本文的相关配置并不只是适用于vite项目,其余项目都可以引入相关配置项,只需要改变一下运行时的相关插件就可以了。

另外,官方为我们提供了对应的社区插件仓库 awesome-vite,可以根据自己的需求看看是否有适合的插件。

后续

后续也会加入包括 Vite 插件编写(如何实现 mock 服务等),打包上线、持续集成服务等操作的文章,感兴趣的小伙伴也可以关注一下专栏,顺便给个👍🏻再走呗~~。