好用的前端工具链
Deep Dive into My Favorite Toolchain: Pitfalls, Highlights, and Unresolved Obsessions.
这篇文章不会写工具如何用,怎么用可以去看官网。这里只有我亲测好用的私藏宝贝、我在折腾它们时的挖坑填坑和让我兴奋的技术延伸。
Vite+
全局安装,详见Vite+官网
为什么我不改package.json 中的命令,本地的vite+也能自动识别vp转化为npm run?
vp dev 并不是被“转化”为 npm run dev,而是 Vite+ 直接“绕过”了 package.json 中的脚本,去调用了底层真正的命令。
对比三种“启动方式”的区别:
| 命令 | 实际执行的动作 | 是否依赖 package.json 的 scripts |
|---|---|---|
npm run dev / pnpm dev | 读取 package.json 中的 "dev" 脚本,执行其中的字符串命令 | 完全依赖 |
vp dev | 忽略 package.json,直接在 node_modules 中找 vite 二进制文件并执行。 | 完全不依赖(只依赖项目是否安装了 vite 包) |
vp 的好用快捷键速记
press r + enter重启开发服务器press u + enter重新显示服务器地址press o + enter自动打开浏览器press c + enter清空终端屏幕press q + enter退出并终止开发服务器
运行 Vite+ 的环境条件
| 条件维度 | 具体要求 | 扩展问题 |
|---|---|---|
| 操作系统 | macOS(10.15+)、Linux、Windows(WSL2 或 PowerShell) | 🤔为什么强调 macOS10.15+? |
| Shell 环境 | Bash、Zsh、Fish 或 PowerShell | - |
| Node.js | Node.js 18.0.0 或更高版本(底层 Vite 的要求) | 🤔为什么要求Node18+? |
| 网络 | 能访问 npm 官方仓库(或配置了国内镜像 | 🤔vp 命令启动是否需要网络? |
INFO
🤔为什么强调 macOS10.15+?
因为从 macOS Catalina(10.15)开始,苹果就把默认 Shell 从 Bash 换成了 Zsh。Vite+ 依赖 Zsh 的一些现代特性(如更完善的 glob 模式、数组操作)来优化脚本执行,因此官方建议 10.15+。实际上,如果你手动装了新版 Bash,在旧版 macOS 上也能跑,但官方不再为此做兼容测试。
😈 额外问题:
🤔为什么要求Node18+? 
🤔vp 命令启动是否需要网络? 
Vite+ 命令运行条件
不是所有项目都能用 Vite+ 的所有命令,这里只讨论 Vite 构建的项目。
| 命令 | 项目必须具备的条件 |
|---|---|
vp dev / vp build | 1. package.json中必须安装 vite 作为依赖。2. 项目根目录必须有 vite.config.js 或 vite.config.ts。3. 必须有一个 index.html 作为入口(标准 SPA)。 |
vp check(代码检查) | 项目中有 .eslintrc 或 oxlintrc.json 配置文件。 |
vp test | 项目中安装了vitest依赖 |
vp pack(打包) | 项目有 src/index.ts 等入口文件,用于发布 npm 包。 |
个人使用vp如何在团队开发中避免污染仓库?
千万不要在 CI/CD(如 GitHub Actions、Jenkins)里用 vp(其实就是不要在本地项目的
yml文件里写vp)全局安装时出现提示
vp env on选择no。如果选了yes,这里Vite+ 会生成 node 替身文件,可能会和公司的 .nvmrc 文件或 nvm 产生“谁说了算”的冲突。
WARNING
我在团队开发拉取代码的时候会发现别人的.DS_Store,这是Mac 系统在每个文件夹下自动生成的隐藏文件,记录图标位置。
解决方法,在项目根目录的.gitignore写:
# macOS
.DS_Store
Thumbs.db
# 编辑器/临时文件
.vscode/
.idea/
*.local
Oh My Zsh
为什么z file不能快速切换文件夹了?
因为z file名,不是“全局搜索工具”,它只记得你去过的地方。
敲 z 加文件夹名字就能瞬间飞过去,这其实是 Oh My Zsh 自带的一个超级插件,叫做z,它能把项目路径极其复杂的简化。启用方式:检查你的 ~/.zshrc 配置文件中的 plugins=(git z),确保 z 在里面。重启终端后,你就可以享受这种“记忆力”了。
硬核功能
除了美化控制台里的命令行、跳转路径外,还有很多其他功能,这里写下来是因为我发现我平时都没物尽其用。
git 插件缩写:gst → git status,gco → git checkout,gcb → git checkout -b, 目测能省下不少键盘寿命。
history 搜索:
输入 r 加关键字,快速找回之前敲过的长命令(比如 r vite 就能翻出 pnpm create vite@latest)
TypeScript
对文档内容的思考和延伸
"TypeScript是带有编译时类型检查器的JavaScript的运行时。"
我的理解是 tsc 是在 js 的基础上多了一个类型检查器的功能。
"通过感知JavaScript的工作原理,TypeScript可以构建一个接受JavaScript代码但具有类型的类型系统。"
let helloWorld = "Hello World";//这样赋值无需声明类型,tsc是知道helloWorld的值是字符串类型的 let helloWorld: string🤔这我想起了几年前写的
vue + ts的项目,是let helloWorld: string = ''的写法,这里我有个疑问,既然tsc直接认识,为什么还要写类型强调?可能是设置了eslint检查,tsc规定,只有当声明变量但不赋值(或赋值为 null/any)时,才必须亲手写类型注解。在TypeScript 官方团队与 ESLint 团队合作维护的typescript-eslint工具链中,有一个配置no-inferrable-types,就是为了禁止tsc能轻易判断出的基本类型进行多余的显示注解。
⚠️,该规则默认不检查函数参数和属性。另外,这个规则也是为了去掉一眼就看得出的废话,维护团队代码一致性。
在Vue项目中推荐开启,可以针对
<script setup>中的普通变量约束,对defineProp、defineEmits里的使用的类型字面量无效果,对ref、computed灵活处理。"如果将代码从 JavaScript 迁移到 TypeScript ,即使 TypeScript 认为代码有类型错误,也可以 保证 以相同的方式运行。"
这句话让我产生疑惑🤔,因为开发时项目中ts爆红,
npm run就跑不动。原因在于实际开发中,前端工程化的工具如Vue/ Vite / Webpack / Nuxt等在调用底层TS时,通常会强制开启类型检查作为构建的前置关卡。所以我对tsc的刻板印象不好用、严格、麻烦并不是tsc本身的原因。
文档的这句话,强调的是逻辑层面的不变性,而不是编译通过性。 如下:
// 在 JS 中 let result = 1 / 0; // 结果是 Infinity // 迁移到 TS 后(即使你写错了类型) let result: number = 1 / 0; // 或者你甚至写 let result: string = 1 / 0;(类型报错) // 但编译出的 JS 依然是 let result = 1 / 0; // 在浏览器里跑,结果依然是 Infinity,而不会像 Java 那样抛出一个 ArithmeticException。"对函数的类型定义"link 由文档衍生的疑问:对错的规则究竟是什么?
interface User { name: string; id: number; } // ---分割线--- const user2 = { name: 'A', id: 1, age: 18 }; function getAdminUser(): User { return user2; // ✅ 通过!鸭式类型判定有效 } getAdminUser({name: 'A', id: 1, age: 18});//❌ 报错 function deleteUser(user: User) { // ... } const extraUser = { name: 'A', id: 1, age: 18 }; deleteUser(extraUser); //✅ 通过 deleteUser({ name: 'A', id: 1, age: 18 }); //❌ 报错一个坑点:只要用字面量,返回值和参数都会严格报错;只要用变量,返回值和参数都宽松放行。
鸭式类型
源自 计算机科学
Computer Science下的子学科 —— 编程语言理论Programming Language Theory, PLT和 类型理论Type Theory。“鸭式类型
Duck Typing”这个词的灵感来自于美国诗人 James Whitcomb Riley 的一句民间谚语:“If it looks like a duck, swims like a duck, and quacks like a duck, then it probably is a duck.”(如果它看起来像鸭子,游泳像鸭子,叫起来像鸭子,那它就是鸭子。)
在编程领域,这个概念最早主要用于
Python、Ruby 等动态语言(运行时检查),后来TypeScript将其引入到静态类型检查中,形成了“结构化类型系统”。即:不关心这个对象的“真实身份(类名)”,只关心它“长什么样(拥有哪些属性/方法)”。“TypeScript 提供了检查你在赋值时与类型是否匹配的功能。”
疑问:🤔既然 TS 能自动识别(类型推断),为什么还要用 interface?
interface User { name: string; id: number; } const user: User = { username: "Hayes", // ❌ Object literal may only specify known properties, and 'username' does not exist in type 'User'. id: 0, };很有趣的问题,但答案AI告诉了我更有趣的类比。tsc的自动识别(类型判断)可以类比验钞机,interface类比验钞模版(规定钱/代码长什么样),你这个疑问就是既然验钞机能识别真钱,为什么还要验钞模版?
验钞模版能在钱生产的源头就避免错误。tsc中的自动识别是用,interface接口定义了一个开发者需要遵守的、公开的、稳定的契约。
延伸:🤔tsc中简单类型会用
type而非interface,tsc对简单类型的定义场景?type和interface的不同?`type`&`interface`
核心区别:

实际开发中的使用场景划分:

Vue 类型声明场景
定义响应式变量 ref 时,如果初始值是空字符串,不要写泛型:
// ❌ 多余(不推荐) const msg = ref<string>(''); // ✅ 正确(TS 自动推断为 Ref<string>) const msg = ref('');只有在初始值为 null 或没有初始值时,才需要写泛型:
// 必须写,因为 null 无法推断出 string const user = ref<UserInfo | null>(null);定义 props 时,必须写(因为这是对象结构的描述,不是变量赋值):
const props = defineProps<{ title: string; // 这里必须写,因为没有初始值给它推断 }>();