本文面向刚接触 uni-app 的开发者(尤其是从原生 iOS/Android 转过来的同学)。如果你打开一个 uni-app 项目,看到根目录下同时躺着
node_modules和uni_modules两个文件夹,脑子里冒出一句"这俩有啥区别?我到底该用哪个?"------那这篇文章就是为你写的。
一、先讲一个故事:原生开发者的困惑
你写了五年 iOS,闭着眼睛都能敲出 pod install。某天领导说:"咱们要搞跨端,你研究一下 uni-app。"
你打开项目,发现根目录下有两个长得很像的文件夹:
go
my-project/
├── node_modules/ ← 这是什么?
├── uni_modules/ ← 这又是什么?
├── package.json
├── ...
你的第一反应是:"uni_modules 是不是就是 uni-app 版的 node_modules?"
答案是:不是。 它们是两个完全不同定位的东西,只是名字长得像。搞混了会走很多弯路。
二、一句话搞清楚
| node_modules | uni_modules | |
|---|---|---|
| 一句话 | 装"纯 JS 逻辑库"的文件夹 | 装"跨端功能插件"的文件夹 |
| 类比 | 你的"螺丝刀、扳手"工具箱 | 你的"发动机总成、变速箱总成"零件库 |
node_modules 里放的是 lodash、dayjs、pinia 这种纯代码逻辑,不涉及任何平台原生能力。
uni_modules 里放的是 DCloud 插件市场的插件,这些插件可能包含 UI 组件、原生 SDK 封装、云函数、页面模板,是一个完整的"功能包"。
三、node_modules 详解:你的 JS 工具箱
3.1 它是什么
node_modules 是 Node.js 生态的标准目录,存放通过 npm install、yarn add、pnpm add 安装的 JavaScript/TypeScript 库。
它不是 uni-app 特有的,任何前端项目(React、Vue、Angular)都有这个目录。uni-app 只是沿用了这个机制。
3.2 里面装什么
| 类别 | 举例 | 说明 |
|---|---|---|
| 状态管理 | pinia, vuex | 管理全局数据 |
| 工具函数 | lodash-es, dayjs, uuid | 日期处理、防抖节流、生成ID |
| 数据校验 | zod, yup | 表单验证、接口数据校验 |
| 类型定义 | @types/xxx | TypeScript 类型提示 |
| 构建工具 | vite, webpack, @dcloudio/xxx | 编译打包相关 |
3.3 怎么用
python
# 安装一个库
npm install dayjs
# 安装并标记为开发依赖
npm install -D @types/lodash-es
# 在代码中直接 import
import dayjs from 'dayjs'
const now = dayjs().format('YYYY-MM-DD HH:mm:ss')
和你在任何 Vue/React 项目中的操作完全一样,没有任何 uni-app 特殊之处。
3.4 什么不能放这里
这是最容易踩坑的地方:
| ❌ 不要装的 | 为什么 | 该怎么做 |
|---|---|---|
| axios | 小程序环境没有 XMLHttpRequest,axios 直接报错 | 用 uni.request() 或 uni_modules 中的网络封装插件 |
| jquery | 依赖 DOM 操作,小程序和 App 端没有 DOM | 用 uni-app 内置的 DOM 操作或组件方式 |
| lottie-web | 只能跑在浏览器里 | App 端去插件市场找 lottie 原生插件 |
| 任何需要调摄像头/蓝牙/GPS 的库 | JS 层无法直接访问硬件 | 去 uni_modules 找对应的原生插件 |
判断标准很简单: 如果一个库需要调用平台原生 API(硬件、系统权限、原生 UI),它就不属于 node_modules,而应该去 uni_modules 找。
3.5 注意事项
- 优先选 ESM 版本 :
lodash-es优于lodash,因为小程序对 CommonJS(require/module.exports)支持不完整 - 检查是否依赖 Node API :
fs、path、Buffer这些在小程序和 App 端不存在 - 控制体积:微信小程序主包限 2MB,别一股脑全装,按需引入
- 配好 .gitignore :
node_modules/要忽略,靠package-lock.json恢复
四、uni_modules 详解:你的跨端能力仓库
4.1 它是什么
uni_modules 是 DCloud 官方定义的插件规范 (HBuilderX 3.1.0+ 引入)。它不是一个包管理器,而是一个目录约定------只要你的插件按照这个规范放文件,HBuilderX 和 uni-app 编译器就能自动识别和加载。
你可以把它理解为:uni-app 的"插件安装目录" 。
4.2 里面装什么
一个 uni_module 插件可以包含以下任意组合:
| 内容 | 说明 | 举例 |
|---|---|---|
| Vue 组件 | 跨端 UI 组件 | 日历选择器、文件上传器 |
| JS SDK | 封装好的 API 函数 | 加密解密、格式化 |
| UTS 原生代码 | 编译为 Swift/Kotlin/ArkTS 的原生实现 | 支付SDK、地图、推送 |
| uniCloud 云函数 | Serverless 后端 | 用户登录、数据存储 |
| 页面模板 | 完整的页面 | 登录页、商品详情页 |
| DB Schema | 数据库校验规则 | 表单自动校验 |
4.3 长什么样(目录结构)
lua
uni_modules/
└── uni-camera-pro/ ← 插件ID(唯一标识)
├── package.json ← 插件的"身份证":名称、版本、依赖
├── readme.md ← 使用文档
├── changelog.md ← 更新日志
│
├── components/ ← Vue 组件
│ └── camera-view.vue
│
├── js_sdk/ ← JS/TS API
│ └── index.ts
│
├── utssdk/ ← 🔑 原生代码(核心!)
│ ├── interface.uts ← 统一接口定义
│ ├── app-ios/ ← iOS 实现
│ │ ├── index.uts
│ │ └── config.json ← 声明用哪些 CocoaPods 库
│ ├── app-android/ ← Android 实现
│ │ ├── index.uts
│ │ └── config.json ← 声明用哪些 Maven 库
│ └── app-harmony/ ← 鸿蒙实现
│ ├── index.uts
│ └── config.json ← 声明用哪些 OHPM 库
│
├── uniCloud-aliyun/ ← 云函数(可选)
└── pages/ ← 页面模板(可选)
4.4 怎么安装
方式一:HBuilderX 插件市场导入(最推荐)
arduino
HBuilderX 菜单栏 → 工具 → 插件安装 → 浏览插件市场
→ 搜索你需要的插件 → 点击"导入插件" → 选择目标项目 → 完成
这是最简单的方式,HBuilderX 会自动帮你:
- 创建
uni_modules/插件名/目录 - 下载所有文件
- 处理子依赖
- 合并 pages.json 配置(如果需要)
方式二:手动复制
markdown
1. 去 https://ext.dcloud.net.cn 搜索插件
2. 下载 zip 包
3. 解压到项目的 uni_modules/ 目录下
4. HBuilderX 自动识别
方式三:Git 引入
bash
# 如果是团队自研的私有插件,放在 Git 仓库中
git clone https://github.com/your-team/uni-payment-sdk.git uni_modules/uni-payment-sdk
⚠️ 重要:不能用
npm install安装 uni_modules 插件。 它们不走 npm registry,有自己独立的分发和识别机制。
4.5 怎么使用
安装完成后,根据插件类型不同,使用方式不同:
Vue 组件类:
xml
<template>
<!-- 直接当组件用,无需 import(easycom 自动识别) -->
<uni-file-picker
file-mediatype="image"
@select="onFileSelect"
/>
</template>
JS SDK 类:
javascript
// 需要手动 import
import { encrypt, decrypt } from '@/uni_modules/my-crypto/js_sdk/index.ts'
const cipher = encrypt('hello world')
UTS 原生插件类:
javascript
// 同样 import 使用
import { pay, isInstalled } from '@/uni_modules/uni-payment-sdk'
// 调用
const result = await pay({ orderId: '12345', amount: 9.9 })
4.6 版本管理
这是 uni_modules 最大的"短板"------没有 lock 文件。
| npm (node_modules) | uni_modules | |
|---|---|---|
| 版本锁定 | ✅ package-lock.json | ❌ 无 |
| 团队一致性保障 | lock 文件 | Git 提交快照 |
所以,最佳实践是:把 uni_modules/ 目录完整纳入 Git 版本管理。
bash
# .gitignore
# ✅ 忽略 node_modules(通过 lock 文件恢复)
node_modules/
# ❌ 绝对不要忽略 uni_modules!
# uni_modules/ ← 不要写这行!!!
# 可以忽略插件内部的 node_modules
uni_modules/*/node_modules/
这样,团队所有人 git pull 之后,拿到的插件版本完全一致,不需要额外的"安装"步骤。
五、两者的关系:不是替代,是协作
很多人以为 uni_modules 是 node_modules 的"替代品",这是错的。它们是并行协作的关系:
csharp
┌─────────────────────────────────────────────────────────┐
│ 你的 uni-app 项目 │
│ │
│ ┌───────────────────┐ ┌─────────────────────────┐ │
│ │ node_modules/ │ │ uni_modules/ │ │
│ │ │ │ │ │
│ │ • pinia │ │ • uni-ui (组件库) │ │
│ │ • dayjs │ │ • uni-file-picker │ │
│ │ • lodash-es │ │ • my-payment-sdk │ │
│ │ • zod │ │ • uni-permission │ │
│ │ │ │ • uni-cloud-jwt │ │
│ │ 纯 JS 逻辑 │ │ 跨端功能插件 │ │
│ │ 不涉及原生 │ │ 可能包含原生代码 │ │
│ └───────────────────┘ └─────────────────────────┘ │
│ │
│ 安装方式:npm install 安装方式:插件市场导入 │
│ 版本保障:lock 文件 版本保障:Git 提交 │
│ 来源:npmjs.com 来源:ext.dcloud.net.cn │
└─────────────────────────────────────────────────────────┘
一个 uni_modules 插件甚至可以依赖 node_modules 中的库:
json
// uni_modules/uni-camera-pro/package.json
{
"dependencies": {
"dayjs": "^1.11.0" // ← 这个插件内部用了 dayjs
}
}
安装插件后,你需要在插件目录内执行 npm install 来安装它的 npm 依赖(或者 HBuilderX 右键"安装插件依赖")。
六、实战决策:我到底该用哪个?
当你需要引入一个三方能力时,按这个流程判断:
objectivec
我需要某个功能(比如:日期格式化 / 支付 / 地图 / 文件上传)
│
▼
这个功能需要调用原生 API 吗?
(摄像头?蓝牙?GPS?推送?支付SDK?地图SDK?)
│
┌─── NO ───┐ YES
▼ ▼ │
纯 JS 逻辑 需要原生能力 │
│ │ │
▼ ▼ ▼
用 npm 去 DCloud 插件市场搜索
│ ext.dcloud.net.cn
▼ │
npm install ┌─── 找到了 ───┐
│ ▼ ▼ 没找到
▼ 导入到 自己写 UTS 插件
完成 ✅ uni_modules/ (封装各端原生 SDK)
│ │
▼ ▼
完成 ✅ 完成 ✅
举几个具体例子:
| 需求 | 判断 | 做法 |
|---|---|---|
| 日期格式化 | 纯 JS,不需要原生 | npm install dayjs → node_modules |
| 全局状态管理 | 纯 JS | npm install pinia → node_modules |
| 微信支付/支付宝支付 | 需要原生 SDK | 插件市场找支付插件 → uni_modules |
| 高德地图 | 需要原生 SDK | 插件市场找地图插件 → uni_modules |
| 图片选择 + 上传 | 需要原生(相册权限) | 插件市场找 uni-file-picker → uni_modules |
| 字符串加密 | 纯 JS | npm install crypto-js → node_modules |
| 蓝牙设备连接 | 需要原生 | 插件市场找蓝牙插件 → uni_modules |
| 表单数据校验 | 纯 JS | npm install zod → node_modules |
七、常见踩坑与 FAQ
Q1:我在 node_modules 里装了 axios,为什么小程序端报错?
因为 axios 底层依赖 XMLHttpRequest,而小程序环境没有这个对象(小程序用的是 wx.request)。解决方案: 用 uni.request() 或封装一个基于 uni.request 的请求库。
Q2:我能不能用 npm install 安装 uni_modules 里的插件?
不能。 uni_modules 插件不发布在 npm registry 上,它有自己的目录结构和识别机制。必须通过 HBuilderX 插件市场导入或手动复制。
Q3:uni_modules 需要加到 .gitignore 吗?
绝对不要。 uni_modules 没有 lock 文件,如果不纳入 Git,你的同事拉取代码后就没有这些插件,项目直接跑不起来。
Q4:node_modules 需要加到 .gitignore 吗?
需要。 node_modules 体积大(动辄几百 MB),而且可以通过 package-lock.json + npm ci 精确恢复,没必要提交到 Git。
Q5:一个插件既用了 npm 库又有原生代码,怎么管理?
这种情况很常见。比如一个相机插件,JS 层用了 dayjs 做时间戳格式化(npm 依赖),原生层用了 SDWebImage(CocoaPods 依赖)。
- npm 依赖 → 写在插件的
package.json的dependencies里 → 在插件目录内npm install - 原生依赖 → 写在
utssdk/app-ios/config.json里 → 构建时自动pod install
两层各管各的,互不干扰。
Q6:我修改了 uni_modules 里插件的源码,升级时会不会被覆盖?
会。如果你从插件市场更新了插件,你手动修改的代码会被覆盖。建议: 如果需要深度定制,Fork 插件源码(购买源码授权版),然后在 Git 中独立管理,不再走插件市场的更新流程。
Q7:CI/CD 中如何处理这两种依赖?
bash
# 1. 恢复 npm 依赖
npm ci # 使用 lock 文件精确恢复
# 2. uni_modules 已在 Git 中,无需额外操作
# 但如果插件内部有 npm 依赖:
for dir in uni_modules/*/; do
if [ -f "$dir/package.json" ]; then
(cd "$dir" && npm install)
fi
done
# 3. 正常构建
npm run build:app-android
八、对照原生世界的类比
如果你是原生开发者,这个对照表可以帮你快速建立认知:
| 原生世界 | uni-app 世界 | 说明 |
|---|---|---|
| CocoaPods / SPM | uni_modules 中的 app-ios/config.json |
管理 iOS 原生 SDK 依赖 |
| Gradle / Maven | uni_modules 中的 app-android/config.json |
管理 Android 原生 SDK 依赖 |
| OHPM | uni_modules 中的 app-harmony/config.json |
管理鸿蒙原生 SDK 依赖 |
| 纯 Swift 算法库(无 UIKit 依赖) | node_modules 中的纯 JS 库 | 不涉及平台 API,哪都能跑 |
| 完整的 iOS SDK(含 UI + 原生 + 资源) | uni_modules 中的跨端插件 | 一个包解决一个完整功能 |
| Podfile.lock | Git 提交(uni_modules 无 lock) | 版本锁定机制 |
九、总结:一张图记住
perl
┌─────────────────────────────────────────────────────────────────┐
│ │
│ node_modules uni_modules │
│ ═══════════ ═══════════ │
│ │
│ 📦 纯 JS 逻辑库 📦 跨端功能插件 │
│ │
│ 来源:npmjs.com 来源:ext.dcloud.net.cn │
│ 安装:npm install 安装:HBuilderX 导入 │
│ 锁定:package-lock.json 锁定:Git 提交 │
│ 能力:仅 JS 运行时 能力:JS + 原生 + 云函数 │
│ │
│ 例子: 例子: │
│ • pinia(状态管理) • uni-ui(UI 组件库) │
│ • dayjs(日期处理) • uni-file-picker(文件选择) │
│ • lodash-es(工具函数) • my-payment(支付 UTS 插件) │
│ • zod(数据校验) • uni-push(推送插件) │
│ │
│ ⚠️ 加到 .gitignore ✅ 必须纳入 Git │
│ │
└─────────────────────────────────────────────────────────────────┘
两者不是替代关系,是协作关系。
一个项目通常两个都有。
十、最后的建议
- 新项目第一件事: 配好
.gitignore(忽略node_modules/,保留uni_modules/) - 纯逻辑需求: 先想能不能用 npm 解决,能用就用,简单直接
- 涉及原生能力: 先去 ext.dcloud.net.cn 搜,90% 的需求都有现成插件
- 找不到现成插件: 学 UTS,自己封装。config.json 就是你的 "Podfile / build.gradle"
- 团队协作: uni_modules 必须提交 Git,这是没有 lock 文件的唯一补救方案
- 不要混用: 别试图把原生 SDK 塞进 node_modules,也别把纯 JS 库放进 uni_modules
记住这句话:node_modules 管"逻辑",uni_modules 管"能力"。逻辑是代码,能力是功能。两者配合,才是一个完整的 uni-app 项目。
本文基于 uni-app 官方文档(uniapp.dcloud.net.cn)及 DCloud 插件市场规范整理。uni-app 框架持续迭代,如有细节变化,以官方最新文档为准。