npm.io
0.5.1 • Published 3h ago

@uni-helper/vite-plugin-uni-manifest

Licence
MIT
Version
0.5.1
Deps
2
Size
21 kB
Vulns
0
Weekly
0
Stars
31

@uni-helper/vite-plugin-uni-manifest

NPM version

使用 TypeScript 编写 uni-appmanifest.json

不想看文档?直接问 AI Ask DeepWiki

安装

pnpm i -D @uni-helper/vite-plugin-uni-manifest

使用

// vite.config.ts
import Uni from '@dcloudio/vite-plugin-uni'
import UniManifest from '@uni-helper/vite-plugin-uni-manifest'
import { defineConfig } from 'vite'

export default defineConfig({
  plugins: [UniManifest(), Uni()]
})

创建 manifest.config.(ts|mts|cts|js|cjs|mjs|json),然后用 TypeScript 编写你的 manifest.json

// manifest.config.ts
import { defineManifestConfig } from '@uni-helper/vite-plugin-uni-manifest'

export default defineManifestConfig({
  // 属性参考 manifest.json,理论上一比一对齐
  // 如果发现没有对齐,请提交 issue,谢谢 🙏
  // https://uniapp.dcloud.net.cn/collocation/manifest.html
  name: 'my-project',
})

这里,你可以找到 uni-app 默认的 Vite-TS 模版的 manifest.json 是如何用 TypeScript 编写的。

插件配置

UniManifest() 支持以下选项定义行为:

interface Options {
  /**
   * 是否压缩生成的 manifest.json(去除缩进和换行)。
   * @default false
   */
  minify?: boolean

  /**
   * 是否在 manifest.json 末尾插入换行。
   * @default false
   */
  insertFinalNewline?: boolean

  /**
   * 解析配置的工作目录。
   * 插件会从该目录查找 `manifest.config.(ts|mts|cts|js|cjs|mjs|json)` 文件。
   * @default process.env.VITE_ROOT_DIR
   */
  cwd?: string

  /**
   * 生成 `manifest.json` 的输出目录。
   * 未设置时使用 uni-app 的 `UNI_INPUT_DIR`(或 `cwd/src`)。
   * 相对路径会基于 `process.cwd()` 解析。
   * @default undefined
   */
  outDir?: string
}
minify

默认生成的 manifest.json 是格式化后的(缩进 2 空格)。默认为 false

开启后会输出紧凑的 JSON,减少文件体积。

UniManifest({ minify: true })
insertFinalNewline

控制在生成的 manifest.json 末尾是否追加一个换行符。默认为 false

开启后符合 POSIX 文件规范,部分工具链可能会要求文件以换行结尾。

UniManifest({ insertFinalNewline: true })
cwd

指定插件查找 manifest.config.* 配置文件的目录。默认为 process.env.VITE_ROOT_DIR(由 @dcloudio/vite-plugin-uni 注入的环境变量),通常无需手动配置。

在 monorepo 场景下,如果你需要从其他目录解析配置,可以显式指定:

UniManifest({ cwd: resolve(__dirname, 'packages/h5') })
outDir

指定生成 manifest.json 的输出目录。默认情况下,插件会写入 uni-app 的输入目录(由 UNI_INPUT_DIR 决定,通常是 src/manifest.json)。

当你需要把 manifest.json 输出到其他位置时(例如自定义的构建产物目录、或临时目录用于后续处理),可以通过 outDir 覆盖默认行为:

UniManifest({ outDir: resolve(__dirname, 'src/manifest-output') })

注意:uni-app 在运行时会从其默认输入目录读取 manifest.json,自定义 outDir 后请确保后续流程能正确读取到该文件,否则可能导致 uni-app 无法解析 manifest。

FAQ

这个插件写入配置晚于 uni-app 读取配置,导致无法正常运行

@dcloudio/vite-plugin-uni 在 Vite 的 config 钩子里通过 parseManifestJsonOnce 读取 manifest.json,而本插件在更晚的 configResolved 钩子里才写入。

config 早于 configResolved,所以即便本插件设置了 enforce: 'pre',也只能在 configResolved 内部抢先,无法早于 config 钩子。parseManifestJsonOnce 结果被 once 缓存,首次读取后即固定,后续写入对 uni-app 无效。

核心矛盾是时序:必须在 uni-app 进程启动前把 manifest.json 生成好。以下按推荐度排序给出方案。

方案一(推荐):使用 @uni-helper/unh

unh 在调用 uni dev/build 前用 unconfig 加载 manifest.config.ts 并写盘,再 spawn 子进程,天然解决时序问题。

// package.json
{
  "scripts": {
    "dev": "unh dev",
    "build": "unh build"
  }
}
// unh.config.ts
import { defineConfig } from '@uni-helper/unh'

export default defineConfig({
  autoGenerate: {
    manifest: true, // 在 dev/build 前自动生成 manifest.json
  },
})
方案二:自行编写脚本,在 uni 命令前生成

用一个独立脚本加载 manifest.config.ts、写入 src/manifest.json,再用 && 串联到 uni 命令前。脚本在 uni 进程之外运行,与 Vite 钩子时序无关。

pnpm i -D c12 tsx
import { writeFileSync } from 'node:fs'
import { resolve } from 'node:path'
import { defineManifestConfig } from '@uni-helper/vite-plugin-uni-manifest'
// scripts/generate-manifest.ts
import { loadConfig } from 'c12'

// 与本插件内部实现保持一致:c12 + defaultConfig 合并
const defaultManifestConfig = defineManifestConfig({
  // ...参考本插件 defaultManifestConfig,或按需精简
})

const { config } = await loadConfig({
  cwd: process.cwd(),
  name: 'manifest',
  defaultConfig: defaultManifestConfig,
  rcFile: false,
  packageJson: false,
})

// UNI_INPUT_DIR 由 vite-plugin-uni 注入,独立脚本中不可用,这里硬编码默认输入目录
// monorepo 或自定义 input dir 时改为对应路径
const outPath = resolve(process.cwd(), 'src/manifest.json')
writeFileSync(outPath, JSON.stringify(config, null, 2))
// package.json
{
  "scripts": {
    "dev:h5": "tsx scripts/generate-manifest.ts && uni",
    "build:mp-weixin": "tsx scripts/generate-manifest.ts && uni build -p mp-weixin"
  }
}

说明:manifest.config.ts 中的热更新监听在本方案下不生效。脚本只生成一次。开发期间修改 manifest.config.ts 需重新执行脚本(或重启 dev server)。本插件仍可作为 Vite 插件保留,用于处理 manifest.config.ts 的运行时变更;首次启动的正确性由本脚本兜底。

方案三:用 npm predev/prebuild 钩子
// package.json
{
  "scripts": {
    "predev": "tsx scripts/generate-manifest.ts",
    "prebuild": "tsx scripts/generate-manifest.ts",
    "dev": "uni",
    "build": "uni build"
  }
}

说明:npm 会在执行 dev/build 前自动运行同名 pre* 脚本。pnpm 10 同样会运行用户自定义的 predev/prebuild(注意 enable-pre-post-scripts 只影响 install 阶段的生命周期脚本,不影响 run 时的 pre*/post*)。比方案二的 && 串联更隐式,开发者可能意识不到 predev 被自动触发,排查问题时需额外留意,故排序靠后。

开发

前置条件
常用命令
# 安装依赖(在 monorepo 根目录执行)
pnpm install

# 构建所有包
pnpm build

# 仅构建 core 包
pnpm -C packages/core build

# 运行测试(在 monorepo 根目录执行)
pnpm test

# 类型检查
pnpm type-check
测试

测试文件位于 monorepo 根目录的 test/ 目录下,使用 Vitest 运行:

test/
├── watcher.test.ts     createManifestWatcher 深模块(选项解析 + 监听 + 写入)
├── plugin.test.ts      插件工厂形状 + 生命周期
├── writer.test.ts      writeManifestJson 格式化 + 幂等写入 + ensureManifestJsonExists
├── paths.test.ts       resolveManifestJsonPath 路径解析
├── defaults.test.ts    默认配置结构
└── config.test.ts      defineManifestConfig 类型断言

watcher.test.tsplugin.test.ts 使用 vi.mock 隔离 c12 和文件系统,确保测试不依赖真实环境。

项目结构
vite-plugin-uni-manifest/
├── packages/
│   ├── core/           插件核心逻辑
│   ├── types/          manifest.json TypeScript 类型定义
│   └── schema/         JSON Schema(从 types 自动生成)
├── test/               测试文件
├── playground/         示例 uni-app 项目
└── pnpm-workspace.yaml

架构

插件围绕一个深模块(watcher.ts)构建,对外暴露极简 interface,内部吸收所有编排逻辑:

index.ts          Vite 插件入口,调用 createManifestWatcher
watcher.ts        深模块 — 选项解析 + c12 配置监听 + 文件写入
writer.ts         文件 I/O — writeManifestJson / ensureManifestJsonExists
paths.ts          路径解析 — resolveManifestJsonPath
defaults.ts       静态数据 — 默认 manifest 配置
config.ts         defineManifestConfig 辅助函数 + 类型重导出
types.ts          公共类型定义(Options / UserOptions / ResolvedOptions)
模块依赖关系
index.ts
  └─ watcher.ts(深模块)
       ├─ writer.ts ── paths.ts
       └─ defaults.ts
插件生命周期

插件通过 Vite 的生命周期钩子驱动,顺序如下:

  1. configResolved(异步)— 调用 createManifestWatcher(userOptions):确保 manifest.json 存在 → 启动 c12 监听 → 执行首次写入
  2. 运行时 — c12 检测到 manifest.config.ts 变更 → onUpdate 回调 → writeManifestJson() 写入文件
  3. buildEnd — 调用 watcher.unwatch() 停止 c12 监听
关键设计决策
  • 深模块设计createManifestWatcher() 是唯一的核心 interface,选项解析、配置监听、文件写入全部作为 implementation 细节隐藏其后。测试直接通过这一个 interface 验证端到端行为。
  • 无导入时副作用:所有文件系统操作(路径解析、文件写入)都在插件生命周期内执行,而非模块导入时。这使得模块可独立测试。
  • 路径解析为函数resolveManifestJsonPath() 每次调用重新计算路径,依赖 process.env.UNI_INPUT_DIR(由 @dcloudio/vite-plugin-uni 注入),不缓存。
  • c12 配置加载:通过 c12watchConfig 实现 manifest.config.ts 的监听和热更新,支持 .ts.mts.js.json 等格式。
  • 幂等写入writeManifestJson 在内容未变化时跳过写入,避免触发下游不必要的重编译。

Keywords