跳到主要内容

Vite+Vue3+TS+Monorepo学习笔记(含问题排查)

· 阅读需 9 分钟

问:在什么情况下可以切换bun?

个人项目可全量用 Bun(开发 + 生产);公司项目优先在「开发环境」全量用 Bun(提速无风险),「生产环境」是否用 Bun 仅需验证 3个点:

  1. 是否深度依赖 Node 原生 API(如 crypto/fs);
  2. 是否有定制化 npm 流程(如私有源 / 脚本钩子);
  3. 构建产物是否和 npm 一致

一、核心知识点汇总

1. 环境基础配置

  • 运行时选择:Bun(开发提速,替代npm/yarn)、Node.js(生产打包兼容,推荐20.19+或22.12+)

  • Node版本管理:使用nvm管理,核心命令: 安装指定版本:nvm install 20.18.0

  • 设置默认版本:nvm alias default 20.18.0

  • 切换版本:nvm use 20.18.0

Monorepo注意点:依赖需安装在子包(vite-vue3)目录,而非根目录;根目录package.json需设private: true

2. Vite核心配置(vite.config.ts)

  • 根级别配置base: './':解决打包后路径错误(非根目录部署必备)

  • plugins: [vue()]:注册Vue插件

server配置port: 5174:自定义开发端口

open: true:启动后自动打开浏览器

proxy:跨域代理,如转发/api到localhost:3000

resolve配置alias: {'@': path.resolve(__dirname, './src')},配置@别名(需配合TS配置)

build配置outDir: 'build':自定义打包输出目录(默认dist)

sourcemap: false:关闭源码映射,减少生产包体积

minify: 'esbuild':使用esbuild压缩(默认,比terser快)

3. 环境变量配置

  • 文件规则.env.development:开发环境(npm run dev生效)

  • .env.production:生产环境(npm run build生效)

命名规则:变量必须以VITE_开头(如VITE_API_URL),否则无法访问

使用方式import.meta.env.VITE_XXX(区别于Webpack的process.env)

4. 核心插件:unplugin-auto-import

  • 作用:自动导入Vue API(ref、reactive等),无需手动写import语句

  • 安装命令bun add -D unplugin-auto-import(优先用Bun避开npm兼容问题)

  • 核心配置AutoImport({ imports: ['vue'], // 自动导入Vue API dts: true, // 生成类型声明文件(auto-imports.d.ts) dirs: ['src/composables'], // 自动导入指定目录下的API eslintrc: { enabled: false } // 暂时关闭eslint干扰 })

  • 类型声明文件:auto-imports.d.ts(生成在vite-vue3根目录,需加入tsconfig.json的include)

5. 生产打包与预览

  • 打包命令npm run build(学习阶段可去掉vue-tsc -b跳过TS检查)

  • 预览命令:使用Vite原生预览(无需装serve),package.json脚本配置: "preview": "vite preview --port 8080 --open"

  • 验证要点:打包生成build文件夹,预览时页面正常渲染,无路径错误

6. 高频面试题(核心答案)

  • Vite和Webpack的核心差异?:① 开发环境:Vite用浏览器原生ESM按需编译(启动快),Webpack全量打包;② 生产环境:Vite用Rollup打包,Webpack自带打包;③ Vite内置编译能力,无需手动配置loader

  • Vite为什么启动快?:① 开发环境不做全量打包,按需编译;② 用esbuild做依赖预构建(比babel快20倍);③ 热更新只更改变动模块,不刷新整个页面

  • Vite如何解决跨域?:通过server.proxy配置接口代理,将前端请求转发到后端服务器,开启changeOrigin: true模拟跨域请求头

二、常见问题及解决方案

1. Node版本相关问题

  • 问题1:执行nvm alias default 20.18.0后,node -v仍显示18.20.5 原因:已打开的终端未加载新配置,或nvm默认版本只对新会话生效

  • 解决:① 手动切换:nvm use 20.18.0;② 重启终端(新会话加载默认版本);③ 终极方案:在~/.zshrc末尾加nvm use 20.18.0 > /dev/null 2>&1,强制启动终端切换

问题2:npm run preview提示Vite需要Node.js 20.19+或22.12+ 原因:Vite版本与当前Node版本(20.18.0)的兼容提示,非强制要求

解决:学习阶段可忽略,不影响功能;需消除提示则升级Node:nvm install 20.19.0 && nvm alias default 20.19.0

2. Vite配置相关问题

  • 问题1:vite.config.ts中base配置标红,提示“base不在类型BuildEnvironmentOptions中” 原因:base是根级别配置,误写在build子配置中

  • 解决:将base: './'移到defineConfig根级别,与server、build平级

问题2:自动生成vite.config.js,导致ts配置不生效 原因:Vite优先加载vite.config.js,覆盖了vite.config.ts

解决:删除根目录下的vite.config.js,确保只保留vite.config.ts

3. 依赖安装与插件相关问题

  • 问题1:npm install -D unplugin-auto-import报错“Cannot read properties of null (reading 'matches')” 原因:npm缓存/配置异常,与Node 20.18.0兼容性问题

  • 解决:① 用Bun安装:bun add -D unplugin-auto-import;② 兜底:npm cache clean --force && npm config delete home后重新安装

问题2:页面正常运行,但IDE标红“找不到名称ref/reactive” 原因:TS未识别auto-imports.d.ts类型声明文件

解决:① 确认auto-imports.d.ts已生成(vite-vue3根目录);② 将其加入tsconfig.json的include;③ 重启TS服务:Cmd+Shift+P → Restart TS Server

问题3:build后未生成auto-imports.d.ts 原因:vue-tsc类型检查报错中断了插件的生成流程,或dev服务未启动过

解决:启动npm run dev(dev模式无TS检查干扰,插件会完整生成文件)

4. 打包与预览相关问题

  • 问题1:bun run build报错“crypto.hash is not a function” 原因:Bun运行时对Node crypto API实现不完整,与@vitejs/plugin-vue@6.x兼容问题

  • 解决:① 改用Node执行打包:nvm use 20 && npm run build;② 兜底:降级插件:bun remove @vitejs/plugin-vue && bun add -D @vitejs/plugin-vue@5.0.4

问题2:npm run build报错“Cannot find name 'ref/reactive'” 原因:vue-tsc类型检查不识别unplugin-auto-import的自动导入API

解决:① 临时方案:修改build脚本为vite build(跳过TS检查);② 规范方案:在src下创建shims-auto-import.d.ts手动声明全局API,并加入tsconfig.json include

问题3:bun run preview报错“Cannot read properties of undefined (reading 'code')” 原因:serve工具与Bun兼容性问题

解决:改用Vite原生预览,package.json脚本配置为"preview": "vite preview --port 8080 --open"

5. IDE相关问题

  • 问题:VSCode搜不到auto-imports.d.ts文件 原因:① 新生成文件的索引延迟;② 误用文本搜索(Cmd+F)而非文件搜索(Cmd+P)

  • 解决:① 用Cmd+P搜索文件名(如auto-imports);② 刷新资源管理器或重启TS服务;③ 关闭再打开项目重新构建索引

三、学习总结

  1. 核心目标达成:掌握Vite+Vue3+TS的基础配置、环境变量、生产打包、核心插件使用,以及常见问题排查方法,能独立搭建可运行的前端项目。

  2. 关键避坑点:① Node版本需固定(避免自动回退);② Vite配置注意层级(如base在根级别);③ 插件生成的类型文件需加入TS配置;④ 生产打包优先用Node执行(兼容性更好)。

  3. 工具使用技巧:① 用nvm管理Node版本,Bun提升开发效率;② VSCode搜索文件用Cmd+P,文本搜索用Cmd+Shift+F;③ 新生成文件需刷新索引或重启服务。

  4. 后续拓展方向:Vite插件开发、Monorepo优化配置、生产环境部署(如Nginx)、Vite与Pinia/Router的集成。

(注:文档部分内容可能由 AI 生成)

tailwind

· 阅读需 6 分钟

第一次接触

前段时间在YouTube看技术类视频发现大量博主在做涉及前端的样式技术选型是几乎是清一色的使用tailwindCSS,这让我不得不想要进一步去了解他

原子类

Tailwind[https://tailwindcss.com/] 的核心是原子化 CSS 类名(如 flex、mt-4、bg-blue-500),每个类名对应唯一、明确的样式规则,无歧义、无自定义命名成本。我想这是ai模型也推荐用它来写样式的原因

使用感受

需要全量记忆对应css属性值的对应类,当然可以根据28原则有限记忆常用的使用频率高的。不过能够预想到,当他成为一种习惯,那么写中小型项目可以极大的提高开发效率,就像五笔打字法速度最快一样(笑),不过有时候要写2行的类名,看起来会让html不清爽。

关于它的一些思考

Tailwind CSS 技术选型分析对比表

思考维度选择 Tailwind 的优势潜在风险 / 不适用场景
开发效率1. 无需切换文件(HTML/CSS),样式写在类名中,开发速度提升 30%+;
2. 内置设计系统(间距、颜色、字体都符合 8px 网格规范),无需手动定义变量;
3. 样式复用靠类名组合,无需写重复 CSS。
1. 类名过长(如 flex items-center justify-between px-4 py-2 bg-blue-500 hover:bg-blue-600),HTML 可读性下降;
2. 新手需记忆类名(如 mt-4=margin-top: 16px),有学习成本。
团队协作1. 原子化类名统一了样式写法,避免 “一人一套命名规范”(如 .btn/.button/.primary-btn);
2. 无需评审 CSS 命名 / 写法,只需关注 UI 还原度。
1. 团队若习惯传统 CSS 模块化(如 BEM 规范),切换成本高;
2. 多人协作时,类名组合可能不统一(如有人用 gap-4,有人用 mx-2 my-2)。
项目场景✅ 适合:
- 快速迭代的业务项目(如电商、后台管理系统);
- 跨端项目(H5、小程序,Tailwind 适配性好);
- 原型 / 演示项目(快速出效果)。
❌ 不适合:
- 极定制化的设计(如品牌官网、创意视觉项目,Tailwind 内置样式无法满足极致定制);
- 超大型老项目(迁移成本高,且易和原有 CSS 冲突)。
性能与打包1. 生产环境可通过 PurgeCSS 剔除未使用的类名,最终 CSS 文件体积通常 < 10KB;
2. 无需担心 CSS 体积随项目增长而膨胀。
1. 开发环境 Tailwind 核心样式体积较大(~3MB),首次加载略慢;
2. 若未配置 PurgeCSS,生产环境体积会失控。
框架 / 工具适配1. 完美兼容 React/Vue/Svelte 等主流框架;
2. 支持 PostCSS、Vite/Webpack 等工具链,可自定义主题(如修改颜色、间距)。
1. 小众框架 / 原生 JS 项目,配置 Tailwind 需额外成本;
2. 自定义主题过多时,会失去 Tailwind “标准化” 的优势。

技术选型

  • 先看项目阶段: 早期快速验证 / 原型开发 → 选 Tailwind(快速出效果); 长期维护的核心项目 → 评估团队接受度,可先小范围试点(如一个页面)。
  • 再看团队能力: 团队以前端为主,学习能力强 → 适合 Tailwind; 团队包含大量后端 / 全栈开发者(不熟悉 CSS) → 适合 Tailwind(降低样式编写门槛); 团队有严格的 CSS 模块化规范(如 BEM) → 谨慎选择(避免冲突)。
  • 最后看定制化需求: 设计稿高度标准化(符合 Tailwind 内置设计系统) → 选 Tailwind; 设计稿有大量自定义样式(如特殊动画、非标准间距) → 可结合 Tailwind + 少量自定义 CSS(而非纯 Tailwind)。

最后

项目中选择 Tailwind,核心是权衡「开发效率」和「维护成本」—— 对快速迭代、团队协作要求高的业务项目,Tailwind 是最优解;对极致定制化、老项目迁移场景,则需谨慎评估或混合使用。

promise

· 阅读需 4 分钟

简单的promise

时间长了对promise原理慢慢有些生疏,记录实现一个简易的promise加强理解。

// 定义 Promise 的三种状态
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';

class MyPromise {
constructor(executor) {
// 初始状态为 pending
this.status = PENDING;
// 存储成功的值
this.value = undefined;
// 存储失败的原因
this.reason = undefined;
// 存储成功回调
this.onFulfilledCallbacks = [];
// 存储失败回调
this.onRejectedCallbacks = [];

const resolve = (value) => {
if (this.status === PENDING) {
this.status = FULFILLED;
this.value = value;
// 依次执行成功回调
this.onFulfilledCallbacks.forEach((callback) => callback());
}
};

const reject = (reason) => {
if (this.status === PENDING) {
this.status = REJECTED;
this.reason = reason;
// 依次执行失败回调
this.onRejectedCallbacks.forEach((callback) => callback());
}
};

try {
// 执行 executor 函数,传入 resolve 和 reject
executor(resolve, reject);
} catch (error) {
// 如果 executor 执行出错,调用 reject
reject(error);
}
}

then(onFulfilled, onRejected) {
// 处理 onFulfilled 不是函数的情况
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : (value) => value;
// 处理 onRejected 不是函数的情况
onRejected = typeof onRejected === 'function' ? onRejected : (reason) => { throw reason; };

const newPromise = new MyPromise((resolve, reject) => {
const handleFulfilled = () => {
try {
const x = onFulfilled(this.value);
// 处理 then 方法返回值,根据规范判断是否递归解析
resolvePromise(newPromise, x, resolve, reject);
} catch (error) {
reject(error);
}
};

const handleRejected = () => {
try {
const x = onRejected(this.reason);
// 处理 then 方法返回值,根据规范判断是否递归解析
resolvePromise(newPromise, x, resolve, reject);
} catch (error) {
reject(error);
}
};

if (this.status === FULFILLED) {
// 如果状态已经是 fulfilled,异步执行成功回调
setTimeout(handleFulfilled, 0);
} else if (this.status === REJECTED) {
// 如果状态已经是 rejected,异步执行失败回调
setTimeout(handleRejected, 0);
} else if (this.status === PENDING) {
// 如果状态还是 pending,将回调存储起来
this.onFulfilledCallbacks.push(() => setTimeout(handleFulfilled, 0));
this.onRejectedCallbacks.push(() => setTimeout(handleRejected, 0));
}
});

return newPromise;
}
}

function resolvePromise(promise, x, resolve, reject) {
if (promise === x) {
return reject(new TypeError('Chaining cycle detected for promise'));
}

let called = false;

if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
try {
const then = x.then;
if (typeof then === 'function') {
then.call(
x,
(y) => {
if (called) return;
called = true;
// 递归解析返回的 promise
resolvePromise(promise, y, resolve, reject);
},
(r) => {
if (called) return;
called = true;
reject(r);
}
);
} else {
resolve(x);
}
} catch (error) {
if (called) return;
called = true;
reject(error);
}
} else {
resolve(x);
}
}

// 以下是测试代码
const promise = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve(100);
}, 1000);
});

promise.then((value) => {
console.log('First then:', value);
return value * 2;
}).then((value) => {
console.log('Second then:', value);
return new MyPromise((resolve) => {
setTimeout(() => {
resolve(value + 50);
}, 1000);
});
}).then((value) => {
console.log('Third then:', value);
});

//导出模块,为测试准备
module.exports = MyPromise;

-安装 ‘promises-aplus-tests’测试工具,并暴露测试适配器

// test.js
const MyPromise = require('../src/somecode/myPromise');

// 包装 MyPromise 以符合测试工具的要求
const adapter = {
resolved: function (value) {
return new MyPromise((resolve) => resolve(value));
},
rejected: function (reason) {
return new MyPromise((_, reject) => reject(reason));
},
deferred: function () {
let resolve, reject;
const promise = new MyPromise((res, rej) => {
resolve = res;
reject = rej;
});
return {
promise,
resolve,
reject
};
}
};

module.exports = adapter;

终端输入 npx '工具名' 文件名 ’

最终结果: 872 passing (16s)

axios

· 阅读需 3 分钟

easy Axios

axios 是一个基于 Promise 的 HTTP 客户端,用于浏览器和 Node.js 环境。简单的实现支持基本的 HTTP 请求方法(如 GET、POST),并能处理请求和响应,目的是为了了解其实现方法。

// 创建一个 Axios 类,用于封装 HTTP 请求的逻辑
class Axios {
// 构造函数,初始化默认配置
constructor() {
// 可以在这里添加更多的默认配置,如超时时间、请求头默认值等
this.defaults = {
baseURL: '',
headers: {
'Content-Type': 'application/json'
}
};
}

// 发送请求的核心方法
request(config) {
// 合并默认配置和用户传入的配置
const mergedConfig = { ...this.defaults, ...config };
const { url, method = 'GET', headers, data } = mergedConfig;

// 返回一个 Promise 对象,用于处理异步请求
return new Promise((resolve, reject) => {
// 创建一个 XMLHttpRequest 对象,用于发送 HTTP 请求
const xhr = new XMLHttpRequest();

// 打开一个请求,设置请求方法和请求 URL
xhr.open(method.toUpperCase(), url, true);

// 设置请求头
for (const header in headers) {
if (headers.hasOwnProperty(header)) {
xhr.setRequestHeader(header, headers[header]);
}
}

// 监听请求状态变化事件
xhr.onreadystatechange = function () {
// 当请求完成且状态码为 200 时,表示请求成功
if (xhr.readyState === XMLHttpRequest.DONE) {
if (xhr.status >= 200 && xhr.status < 300) {
// 解析响应数据
let responseData;
try {
responseData = JSON.parse(xhr.responseText);
} catch (error) {
responseData = xhr.responseText;
}
// 构建响应对象
const response = {
data: responseData,
status: xhr.status,
statusText: xhr.statusText,
headers: xhr.getAllResponseHeaders(),
config: mergedConfig
};
// 解决 Promise,返回响应对象
resolve(response);
} else {
// 请求失败,拒绝 Promise 并返回错误信息
reject(new Error(`Request failed with status code ${xhr.status}`));
}
}
};

// 监听请求错误事件
xhr.onerror = function () {
// 请求发生网络错误,拒绝 Promise 并返回错误信息
reject(new Error('Network Error'));
};

// 发送请求,如果有数据则将数据作为请求体发送
xhr.send(data ? JSON.stringify(data) : null);
});
}

// 封装 GET 请求方法
get(url, config = {}) {
return this.request({ ...config, method: 'GET', url });
}

// 封装 POST 请求方法
post(url, data = {}, config = {}) {
return this.request({ ...config, method: 'POST', url, data });
}
}

// 创建一个 axios 实例,方便使用
const axios = new Axios();

// 示例使用
// 发送一个 GET 请求
axios.get('https://jsonplaceholder.typicode.com/todos/1')
.then(response => {
console.log('GET Response:', response.data);
})
.catch(error => {
console.error('GET Error:', error.message);
});

// 发送一个 POST 请求
axios.post('https://jsonplaceholder.typicode.com/posts', {
title: 'foo',
body: 'bar',
userId: 1
})
.then(response => {
console.log('POST Response:', response.data);
})
.catch(error => {
console.error('POST Error:', error.message);
});