copy-native-resources.ts 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. import type { Plugin } from 'vite'
  2. import path from 'node:path'
  3. import process from 'node:process'
  4. import fs from 'fs-extra'
  5. /**
  6. * 原生插件资源复制配置接口
  7. *
  8. * 根据 UniApp 官方文档:https://uniapp.dcloud.net.cn/plugin/native-plugin.html#%E6%9C%AC%E5%9C%B0%E6%8F%92%E4%BB%B6-%E9%9D%9E%E5%86%85%E7%BD%AE%E5%8E%9F%E7%94%9F%E6%8F%92%E4%BB%B6
  9. * 本地插件应该存储在项目根目录的 nativeplugins 目录下
  10. */
  11. export interface CopyNativeResourcesOptions {
  12. /** 是否启用插件 */
  13. enable?: boolean
  14. /**
  15. * 源目录路径,相对于项目根目录
  16. * 默认为 'nativeplugins',符合 UniApp 官方规范
  17. * @see https://uniapp.dcloud.net.cn/plugin/native-plugin.html#%E6%9C%AC%E5%9C%B0%E6%8F%92%E4%BB%B6-%E9%9D%9E%E5%86%85%E7%BD%AE%E5%8E%9F%E7%94%9F%E6%8F%92%E4%BB%B6
  18. */
  19. sourceDir?: string
  20. /**
  21. * 目标目录名称,构建后在 dist 目录中的文件夹名
  22. * 默认为 'nativeplugins',与源目录保持一致
  23. */
  24. targetDirName?: string
  25. /** 是否显示详细日志,便于调试和监控复制过程 */
  26. verbose?: boolean
  27. /** 自定义日志前缀,用于区分不同插件的日志输出 */
  28. logPrefix?: string
  29. }
  30. /**
  31. * 默认配置
  32. *
  33. * 根据 UniApp 官方文档规范设置默认值:
  34. * - sourceDir: 'nativeplugins' - 符合官方本地插件存储规范
  35. * - targetDirName: 'nativeplugins' - 构建后保持相同的目录结构
  36. */
  37. const DEFAULT_OPTIONS: Required<CopyNativeResourcesOptions> = {
  38. enable: true,
  39. sourceDir: 'nativeplugins',
  40. targetDirName: 'nativeplugins',
  41. verbose: true,
  42. logPrefix: '[copy-native-resources]',
  43. }
  44. /**
  45. * UniApp 原生插件资源复制插件
  46. *
  47. * 功能说明:
  48. * 1. 解决 UniApp 使用本地原生插件时,打包后原生插件资源找不到的问题
  49. * 2. 将项目根目录下的 nativeplugins 目录复制到构建输出目录中
  50. * 3. 支持 Android 和 iOS 平台的原生插件资源复制
  51. * 4. 仅在 app 平台构建时生效,其他平台(H5、小程序)不执行
  52. *
  53. * 使用场景:
  54. * - 使用了 UniApp 本地原生插件(非云端插件)
  55. * - 原生插件包含额外的资源文件(如 .so 库文件、配置文件等)
  56. * - 需要在打包后保持原生插件的完整目录结构
  57. *
  58. * 官方文档参考:
  59. * @see https://uniapp.dcloud.net.cn/plugin/native-plugin.html#%E6%9C%AC%E5%9C%B0%E6%8F%92%E4%BB%B6-%E9%9D%9E%E5%86%85%E7%BD%AE%E5%8E%9F%E7%94%9F%E6%8F%92%E4%BB%B6
  60. * @see https://uniapp.dcloud.net.cn/tutorial/nvue-api.html#dom
  61. *
  62. * @param options 插件配置选项
  63. * @returns Vite 插件对象
  64. */
  65. export function copyNativeResources(options: CopyNativeResourcesOptions = {}): Plugin {
  66. const config = { ...DEFAULT_OPTIONS, ...options }
  67. // 如果插件被禁用,返回一个空插件
  68. if (!config.enable) {
  69. return {
  70. name: 'copy-native-resources-disabled',
  71. apply: 'build',
  72. writeBundle() {
  73. // 插件已禁用,不执行任何操作
  74. },
  75. }
  76. }
  77. return {
  78. name: 'copy-native-resources',
  79. apply: 'build', // 只在构建时应用
  80. enforce: 'post', // 在其他插件执行完毕后执行
  81. async writeBundle() {
  82. const { sourceDir, targetDirName, verbose, logPrefix } = config
  83. try {
  84. // 获取项目根目录路径
  85. const projectRoot = process.cwd()
  86. // 构建源目录绝对路径(项目根目录下的 nativeplugins 目录)
  87. const sourcePath = path.resolve(projectRoot, sourceDir)
  88. // 构建目标路径:dist/[build|dev]/[platform]/nativeplugins
  89. // buildMode: 'build' (生产环境) 或 'dev' (开发环境)
  90. // platform: 'app' (App平台) 或其他平台标识
  91. const buildMode = process.env.NODE_ENV === 'production' ? 'build' : 'dev'
  92. const platform = process.env.UNI_PLATFORM || 'app'
  93. const targetPath = path.resolve(
  94. projectRoot,
  95. 'dist',
  96. buildMode,
  97. platform,
  98. targetDirName,
  99. )
  100. // 检查源目录是否存在
  101. // 如果不存在 nativeplugins 目录,说明项目没有使用本地原生插件
  102. const sourceExists = await fs.pathExists(sourcePath)
  103. if (!sourceExists) {
  104. if (verbose) {
  105. console.warn(`${logPrefix} 源目录不存在,跳过复制操作`)
  106. console.warn(`${logPrefix} 源目录路径: ${sourcePath}`)
  107. console.warn(`${logPrefix} 如需使用本地原生插件,请在项目根目录创建 nativeplugins 目录`)
  108. console.warn(`${logPrefix} 并按照官方文档放入原生插件文件`)
  109. console.warn(`${logPrefix} 参考: https://uniapp.dcloud.net.cn/plugin/native-plugin.html`)
  110. }
  111. return
  112. }
  113. // 检查源目录是否为空
  114. // 如果目录存在但为空,也跳过复制操作
  115. const sourceFiles = await fs.readdir(sourcePath)
  116. if (sourceFiles.length === 0) {
  117. if (verbose) {
  118. console.warn(`${logPrefix} 源目录为空,跳过复制操作`)
  119. console.warn(`${logPrefix} 源目录路径: ${sourcePath}`)
  120. console.warn(`${logPrefix} 请在 nativeplugins 目录中放入原生插件文件`)
  121. }
  122. return
  123. }
  124. // 确保目标目录及其父目录存在
  125. await fs.ensureDir(targetPath)
  126. if (verbose) {
  127. console.log(`${logPrefix} 开始复制 UniApp 本地原生插件...`)
  128. console.log(`${logPrefix} 源目录: ${sourcePath}`)
  129. console.log(`${logPrefix} 目标目录: ${targetPath}`)
  130. console.log(`${logPrefix} 构建模式: ${buildMode}`)
  131. console.log(`${logPrefix} 目标平台: ${platform}`)
  132. console.log(`${logPrefix} 发现 ${sourceFiles.length} 个原生插件文件/目录`)
  133. }
  134. // 执行文件复制操作
  135. // 将整个 nativeplugins 目录复制到构建输出目录
  136. await fs.copy(sourcePath, targetPath, {
  137. overwrite: true, // 覆盖已存在的文件,确保使用最新版本
  138. errorOnExist: false, // 如果目标文件存在不报错
  139. preserveTimestamps: true, // 保持文件的时间戳
  140. })
  141. if (verbose) {
  142. console.log(`${logPrefix} ✅ UniApp 本地原生插件复制完成`)
  143. console.log(`${logPrefix} 已成功复制 ${sourceFiles.length} 个文件/目录到构建目录`)
  144. console.log(`${logPrefix} 原生插件现在可以在 App 中正常使用`)
  145. }
  146. }
  147. catch (error) {
  148. console.error(`${config.logPrefix} ❌ 复制 UniApp 本地原生插件失败:`, error)
  149. console.error(`${config.logPrefix} 错误详情:`, error instanceof Error ? error.message : String(error))
  150. console.error(`${config.logPrefix} 请检查源目录权限和磁盘空间`)
  151. // 不抛出错误,避免影响整个构建过程,但会记录详细的错误信息
  152. }
  153. },
  154. }
  155. }
  156. /**
  157. * 创建 UniApp 本地原生插件资源复制插件的便捷函数
  158. *
  159. * 这是一个便捷的工厂函数,用于快速创建插件实例
  160. * 特别适用于在 vite.config.ts 中进行条件性插件配置
  161. *
  162. * 使用示例:
  163. * ```typescript
  164. * // 在 vite.config.ts 中
  165. * plugins: [
  166. * // 仅在 app 平台且启用时生效
  167. * UNI_PLATFORM === 'app'
  168. * ? createCopyNativeResourcesPlugin(
  169. * VITE_COPY_NATIVE_RES_ENABLE === 'true',
  170. * { verbose: mode === 'development' }
  171. * )
  172. * : null,
  173. * ]
  174. * ```
  175. *
  176. * @param enable 是否启用插件,通常通过环境变量控制
  177. * @param options 其他配置选项,不包含 enable 属性
  178. * @returns Vite 插件对象
  179. */
  180. export function createCopyNativeResourcesPlugin(
  181. enable: boolean = true,
  182. options: Omit<CopyNativeResourcesOptions, 'enable'> = {},
  183. ): Plugin {
  184. return copyNativeResources({ enable, ...options })
  185. }