前言
在公司试着用vite搭建了项目,总体来说收获与坑并存吧。一方面vite确实给了我极好的开发体验,另一方面由于它本身出现的时间比较晚,社区生态并不是很完善,所以很多坑还是要自己去踩。不过幸运的是最后都找到了解决办法,后续也准备写几篇文章来细说一下vite里面的一些坑和实践,下面是第一篇文章,vite的相关环境搭建部分。
什么是 vite
vite是一个新型的开发构建工具,开发环境使用esbuild预构建依赖过程,生产环境中使用rollup作为打包工具以适配更好的性能,并以 原生 ESM 方式提供源码。
也就是说,让浏览器接管了打包程序的部分工作,vite只需要在浏览器请求源码时进行转换并按需提供源码。根据情景动态导入代码,即只在当前屏幕上实际使用时才会被处理。
附一张官方的图:
它的出现主要是为了解决以下现实问题:
- 缓慢的服务器启动:以前基于打包器的方式的构建工具(比如 webpack)冷启动开发服务器时,启动必须优先抓取并构建整个应用才能提供服务,当项目越来越大时会非常的耗时。
- 缓慢的更新:基于打包器启动时,重建整个包的效率很低,原因同上。
初始化 vite 项目
由于作者的技术栈是
React,并且项目多以typescript为主,所以本文的示例暂且都以React + TS的方式组织,一些需要额外说明的地方会针对不同框架进行说明。
npm init vite@latest
# or
yarn create vite
初始化完成后,可以cd进目录,项目的结构很简单:
创建完项目后第一步看下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.ts和tsconfig.json(或vite.config.js和jsconfig.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相关 lintnpm install @typescript-eslint/parser @typescript-eslint/eslint-plugin -D - 添加
ES6模块导入相关 lintnpm install eslint-plugin-import eslint-import-resolver-typescript -Deslint-import-resolver-typescript用于识别ts的中的模块。 - 添加
React相关 lintnpm 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 - 下载
vscode的eslint插件 - 下载
vite运行时插件:
加入npm install vite-plugin-eslint -Dvite.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/**/*'], } - 下载
vscode的prettier插件 - 下载
vite运行时插件:
加入npm install @amatlash/vite-plugin-stylelint -Dvite.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 - 下载
vscode的prettier插件
解决 eslint、stylelint 与 prettier 的冲突
因为我们的eslint和stylelint都是使用的prettier格式的 lint,一般来说都是可以识别项目中的prettier配置的,如果识别不了,一个简单的方法是直接在eslint或stylelint的prettier/prettier中手动同步prettier中的配置(这也是我为什么推荐使用js书写命名文件的原因)。
在.eslintrc.js和stylelint.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>)
其中scope、body、footer都是可选的,比如:
git commit -m "feat: init"
# or
git commit -m "feat(project): init"
规范了信息之后,我们还可以 commit 的内容生成更新日志,非常的方便。下面就是通过 git commit message 自动生成的日志:
所以我们需要在每次提交的时候将不符合要求的提交信息拦截,保留正确格式的提交信息。我这边使用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 -ggit cz代替git commit执行git命令 - 局部使用
然后使用npm install commitizen -Dnpx cz执行git命令,当然也可以直接写在package.json的scripts脚本里面:{ "scripts": { "commit": "cz" } }
当然,还是加入angular的提交格式(全局和局部使用都是安装在本项目中):
npm install cz-conventional-changelog -D
在项目中新建一个.czrc文件:
{
"path": "cz-conventional-changelog"
}
然后就可以愉快的使用了。
生成提交日志
最后,如果我们还想要生成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 服务:
- 下载
huskynpm 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 了
总结
本文为 Vite 开发实践 的第一篇文章,从初始化项目开始完整的搭建了一个配置详细的工程化项目,目前本文的相关配置也已经发布到了github的相关启动模板上,感兴趣的小伙伴可以直接使用。
注意: 本文的相关配置并不只是适用于vite项目,其余项目都可以引入相关配置项,只需要改变一下运行时的相关插件就可以了。
另外,官方为我们提供了对应的社区插件仓库 awesome-vite,可以根据自己的需求看看是否有适合的插件。
后续
后续也会加入包括 Vite 插件编写(如何实现 mock 服务等),打包上线、持续集成服务等操作的文章,感兴趣的小伙伴也可以关注一下专栏,顺便给个👍🏻再走呗~~。