这不是一篇简单的操作教程,而是一份真实的对话记录与思考复盘。通过记录我在部署过程中遇到的每一个疑问,以及如何一步步找到答案,帮助未来的自己真正理解每个环节
第一部分:为什么需要私有 npm 仓库?
我的疑问: 为什么要自己搭一个 npm 仓库?直接用 npm 官方源不就行了吗?
答案:
-
公司内部有多个项目需要共享组件时,不可能把代码发到公共 npm
-
私有组件需要版本管理,和公共包一样方便
-
团队协作时,
npm install就能自动安装私有包
理解: 私有仓库本质是一个"缓存 + 权限管理"的服务。它既存储我们自己发布的包,也可以代理公共包,加速下载。
第二部分:部署 Verdaccio
2.1 为什么要用 Docker 部署?
我的疑问: 直接 npm install verdaccio 不行吗?为什么要用 Docker?
答案:
-
Docker 保证环境一致性,不管在什么系统上运行结果都一样
-
数据持久化方便,容器删了数据还在
-
管理和升级更简单,一条命令就能启动/停止
理解: Docker 就像给应用装了一个"盒子",里面自带了运行所需的一切环境。服务器上只需要有 Docker,就能跑任何应用。
2.2 挂载目录是什么意思?服务器目录和容器目录有什么区别?
我的疑问:
docker run -v /opt/verdaccio/storage:/verdaccio/storage
这行命令里,/opt/verdaccio/storage 和 /verdaccio/storage 分别代表什么?为什么要有两个目录?
答案:
-
/verdaccio/storage是容器内部的目录,由 Verdaccio 程序自己决定,不能改 -
/opt/verdaccio/storage是服务器上的目录,可以随便起名字 -
挂载就是把服务器的目录"映射"到容器内部,这样容器写数据时,实际上写到了服务器上
理解:
服务器真实目录 ←→ 容器虚拟目录
/opt/verdaccio/storage ←→ /verdaccio/storage
容器以为自己在操作 /verdaccio/storage,实际数据在 /opt/verdaccio/storage。这样即使删除容器,数据还在服务器上。
2.3 plugins 路径是什么?要不要配置?
我的疑问:
plugins: /verdaccio/plugins
这个配置是什么意思?我需不需要?
答案:
-
这是告诉 Verdaccio 去哪里找插件(如 GitLab 登录、S3 存储等)
-
如果不需要扩展功能,可以注释掉或删除
-
我目前不需要,所以没配置
理解: 配置文件要"按需配置",不需要的功能就不要写,保持简洁。
2.4 为什么添加用户时报 500 错误?
我的疑问:
npm error 500 Internal Server Error
明明部署成功了,为什么不能注册用户?
答案: 目录权限问题。Verdaccio 容器以用户 10001 运行,但服务器上的目录所有者是 root,容器没有写入权限。
解决方案:
chown -R 10001:65533 /opt/verdaccio
docker restart verdaccio
理解: 容器内的用户(10001)和服务器上的用户(root)是隔离的。容器要写入服务器目录,必须确保该目录对容器用户有写入权限。
第三部分:创建 Monorepo 项目
3.1 为什么要用 Monorepo?
我的疑问: 为什么要把组件和工具函数放在同一个仓库?分开不行吗?
答案:
-
组件库和工具函数版本需要同步更新时,放在一起更方便
-
共享依赖,节省磁盘空间
-
统一的构建和发布流程
理解: Monorepo 就像一个大文件夹,里面放了多个相关的小项目。它们可以独立版本管理,但又可以方便地互相引用。
3.2 pnpm-workspace.yaml 中的 packages 是什么意思?
我的疑问:
yaml
packages:
- "packages/*"
- "playground"
packages/* 代表什么?为什么不能写成 packages?
答案:
-
packages/*匹配 packages 下的直接子目录(如 components、utils) -
如果写成
packages,只匹配 packages 目录本身,不匹配子目录 -
通配符
*很重要,决定了哪些目录被识别为独立包
理解:
-
packages/*→ 匹配 packages/components、packages/utils -
packages→ 只匹配 packages(如果 packages 本身是一个包) -
packages/**→ 匹配所有子目录(包括嵌套的)
3.3 playground 也在 workspace 里,会不会被构建?
我的疑问: 执行 pnpm -r run build 时,playground 也会被构建吗?它只是一个调试环境,不应该被发布。
答案: 会!因为 playground 在 workspace 中,且有 build 脚本,所以会被执行。
解决方案:
packages:
- "packages/*"
# - "playground" # 注释掉,不加入 workspace
或者使用 --filter 过滤:
json
{
"scripts": {
"build": "pnpm --filter \"./packages/*\" run build"
}
}
理解: workspace 是"工作区"概念,所有在里面的包都会被 pnpm 管理。如果某个目录不需要被构建发布,就不要放进 workspace。
第四部分:开发组件
4.1 组件命名:Button 还是 MyButton?
我的疑问:
app.component("MyButton", Button);
为什么要有两个名字?一个 Button,一个 MyButton?
答案:
-
Button是JavaScript 导入名 :import { Button } from '...' -
MyButton是Vue 模板标签名 :<MyButton />
理解: 两个名字服务于不同场景:
-
按需导入时用
Button -
全局注册后在模板中用
MyButton -
这样可以避免与原生 HTML 标签
<button>冲突
4.2 默认导出和命名导出有什么区别?
我的疑问:
export default Button // 默认导出
export { Button } // 命名导出
这两个可以共存吗?能不能去掉一个?
答案: 可以共存,服务于不同使用场景:
-
默认导出 :
import Button from './Button'(简洁) -
命名导出 :
import { Button } from './index'(按需导入)
理解:
默认导出 → 导入时可以任意取名
命名导出 → 导入时必须用原名字(或重命名)
同时保留两种导出,让使用者更灵活。
4.3 样式文件:为什么一定要导入?
我的疑问: 组件里明明写了 <style scoped>,为什么使用时还要 import '@locfly/vue-components/style.css'?
答案: 因为构建工具会把所有样式提取到单独的 CSS 文件,而不是内嵌在 JS 里。
理解
开发时:<style scoped> 写在 .vue 文件里
构建后:样式被抽离到 style.css
使用时:必须导入这个 CSS 文件,样式才能生效
无论是否使用 scoped,样式都会被提取。这是组件库的标准设计。
第五部分:打包和发布
5.1 package.json 的 main 字段为什么要指向 dist?
我的疑问:
"main": "./dist/index.js"
为什么不能指向源码 ./src/index.ts?
答案:
-
用户安装后运行的是 JS 代码,不是 TypeScript
-
.ts文件在 node_modules 里无法直接执行 -
必须指向构建后的 JS 文件
理解:
源码 (.vue, .ts) → 构建 → 产物 (.js, .d.ts, .css)
↓
用户安装使用
用户不需要看到你的源码,只需要构建好的产物。
5.2 files 字段有什么作用?
我的疑问:
json
"files": ["dist", "README.md"]
答案: 告诉 npm 发布时只上传这些文件,其他文件(源码、测试、配置文件)都不会上传。
理解: 这样包体积更小,用户下载更快,也不会暴露源码。
5.3 publishConfig 是做什么的?
我的疑问:
"publishConfig": {
"registry": "http://47.108.252.189:4873/"
}
答案: 指定发布到哪个仓库。即使全局 registry 是淘宝镜像,发布时也会用这个地址。
理解:
npm config get registry → https://registry.npmmirror.com (下载用)
publishConfig.registry → http://47.108.252.189:4873 (发布用)
下载和发布可以指向不同的源,互不影响。
5.4 发布时怎么知道是发到私有仓库?
我的疑问: 执行 npm publish 时,系统怎么知道要发到私有仓库而不是公共 npm?
答案: 优先级顺序:
-
package.json中的publishConfig.registry(最高) -
命令行
--registry参数 -
项目级
.npmrc -
用户级
.npmrc -
npm 全局配置
-
默认源
理解: 我在 package.json 里配置了 publishConfig.registry,所以无论在哪里执行 npm publish,都会发到私有仓库。
5.5 当用这个"publish:all": "pnpm run build && pnpm -r publish --access public",命令来发布的时候,打包是playground一起打包了,但是为什么没传到私有仓库,只是传了component以及utils
核心原因:package.json中的private: true 阻止了发布
第六部分:使用私有包
6.1 .npmrc 配置了私有源,会不会所有包都从私有仓库下?
我的疑问:
ini
registry=http://47.108.252.189:4873/
这样配置后,安装 vue 也会去私有仓库找吗?
答案: 会!但你的 Verdaccio 配置了代理(uplinks),找不到的包会自动去淘宝镜像下载。
理解: 私有仓库像一个"中间人":
-
请求先到私有仓库
-
私有仓库检查本地有没有
-
有则返回,没有则去代理源下载并缓存
6.2 怎么控制部分包从私有仓库下载,部分从公共源?
我的疑问: 我不想所有包都经过私有仓库,能不能分开?
答案: 使用 scope 配置:
ini
# 只有 @locfly 开头的包走私有仓库
@locfly:registry=http://47.108.252.189:4873/
# 其他包走淘宝镜像
registry=https://registry.npmmirror.com/
理解:
-
@locfly/vue-components→ 从私有仓库下载 -
vue→ 从淘宝镜像下载 -
完美分离!
6.3 为什么安装私有包后,组件没有样式?
我的疑问: 组件能显示,但样式全没了。
答案: 没有导入 CSS 文件!
// 只导入了组件
import { Button } from '@locfly/vue-components'
// 缺少样式导入 ❌
解决方案:
typescript
import { Button } from '@locfly/vue-components'
import '@locfly/vue-components/style.css' // ✅ 必须导入
理解: 组件库的样式是独立的,不会自动注入。这是为了支持按需加载和 tree-shaking。
第七部分:整个流程的心智模型
经过这次实践,我建立了以下心智模型:
7.1 部署阶
服务器上运行 Verdaccio 容器
↓
容器读写 /verdaccio/storage
↓
实际映射到 /opt/verdaccio/storage(数据持久化)
↓
对外暴露 4873 端口
7.2 开发阶段
编写组件(.vue)和工具函数(.ts)
↓
playground 通过 alias 直接引用源码(热更新)
↓
开发完成后执行 build
↓
生成 dist/index.js + dist/style.css + dist/*.d.ts
7.3 发布阶段
执行 npm publish
↓
读取 publishConfig.registry(私有仓库)
↓
读取 files 字段(只打包 dist)
↓
执行 prepublishOnly(自动构建)
↓
上传到私有仓库
7.4 使用阶段
配置 .npmrc(@locfly:registry)
↓
npm install @locfly/vue-components
↓
从私有仓库下载 dist 文件
↓
导入组件 + 导入样式
↓
在模板中使用 <MyButton />
第八部分:问题速查表
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 添加用户 500 错误 | 目录权限不足 | chown -R 10001:65533 /opt/verdaccio |
| 发布 409 Conflict | 用户已存在未登录 | npm login |
| 样式不生效 | 未导入 CSS | import '@locfly/vue-components/style.css' |
| 找不到模块 | .npmrc 配置错误 | 检查 @locfly:registry 配置 |
| 发布到公共仓库 | 缺少 publishConfig | 添加 publishConfig.registry |
| defineProps 警告 | 不需要导入 | 删除 import { defineProps } |
结语
这次学习最大的收获不是"学会了部署",而是理解了每一个命令背后的原理。
-
知道了 Docker 挂载是为了数据持久化
-
知道了 pnpm workspace 是为了管理多包
-
知道了 exports 字段是为了控制包的导出方式
-
知道了 publishConfig 是为了分离下载和发布的源
-
知道了样式必须单独导入是因为构建时被提取了
希望未来的自己再看这篇文章时,能快速回忆起这些知识点。如果还有其他疑问,随时可以继续探索!
本回答由 AI 生成,内容仅供参考,请仔细甄别。