You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

202 lines
7.7 KiB
TypeScript

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

import type { Plugin } from 'vite'
import path from 'node:path'
import process from 'node:process'
import fs from 'fs-extra'
/**
* 原生插件资源复制配置接口
*
* 根据 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
* 本地插件应该存储在项目根目录的 nativeplugins 目录下
*/
export interface CopyNativeResourcesOptions {
/** 是否启用插件 */
enable?: boolean
/**
* 源目录路径,相对于项目根目录
* 默认为 'nativeplugins',符合 UniApp 官方规范
* @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
*/
sourceDir?: string
/**
* 目标目录名称,构建后在 dist 目录中的文件夹名
* 默认为 'nativeplugins',与源目录保持一致
*/
targetDirName?: string
/** 是否显示详细日志,便于调试和监控复制过程 */
verbose?: boolean
/** 自定义日志前缀,用于区分不同插件的日志输出 */
logPrefix?: string
}
/**
* 默认配置
*
* 根据 UniApp 官方文档规范设置默认值:
* - sourceDir: 'nativeplugins' - 符合官方本地插件存储规范
* - targetDirName: 'nativeplugins' - 构建后保持相同的目录结构
*/
const DEFAULT_OPTIONS: Required<CopyNativeResourcesOptions> = {
enable: true,
sourceDir: 'nativeplugins',
targetDirName: 'nativeplugins',
verbose: true,
logPrefix: '[copy-native-resources]',
}
/**
* UniApp 原生插件资源复制插件
*
* 功能说明:
* 1. 解决 UniApp 使用本地原生插件时,打包后原生插件资源找不到的问题
* 2. 将项目根目录下的 nativeplugins 目录复制到构建输出目录中
* 3. 支持 Android 和 iOS 平台的原生插件资源复制
* 4. 仅在 app 平台构建时生效其他平台H5、小程序不执行
*
* 使用场景:
* - 使用了 UniApp 本地原生插件(非云端插件)
* - 原生插件包含额外的资源文件(如 .so 库文件、配置文件等)
* - 需要在打包后保持原生插件的完整目录结构
*
* 官方文档参考:
* @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
* @see https://uniapp.dcloud.net.cn/tutorial/nvue-api.html#dom
*
* @param options 插件配置选项
* @returns Vite 插件对象
*/
export function copyNativeResources(options: CopyNativeResourcesOptions = {}): Plugin {
const config = { ...DEFAULT_OPTIONS, ...options }
// 如果插件被禁用,返回一个空插件
if (!config.enable) {
return {
name: 'copy-native-resources-disabled',
apply: 'build',
writeBundle() {
// 插件已禁用,不执行任何操作
},
}
}
return {
name: 'copy-native-resources',
apply: 'build', // 只在构建时应用
enforce: 'post', // 在其他插件执行完毕后执行
async writeBundle() {
const { sourceDir, targetDirName, verbose, logPrefix } = config
try {
// 获取项目根目录路径
const projectRoot = process.cwd()
// 构建源目录绝对路径(项目根目录下的 nativeplugins 目录)
const sourcePath = path.resolve(projectRoot, sourceDir)
// 构建目标路径dist/[build|dev]/[platform]/nativeplugins
// buildMode: 'build' (生产环境) 或 'dev' (开发环境)
// platform: 'app' (App平台) 或其他平台标识
const buildMode = process.env.NODE_ENV === 'production' ? 'build' : 'dev'
const platform = process.env.UNI_PLATFORM || 'app'
const targetPath = path.resolve(
projectRoot,
'dist',
buildMode,
platform,
targetDirName,
)
// 检查源目录是否存在
// 如果不存在 nativeplugins 目录,说明项目没有使用本地原生插件
const sourceExists = await fs.pathExists(sourcePath)
if (!sourceExists) {
if (verbose) {
console.warn(`${logPrefix} 源目录不存在,跳过复制操作`)
console.warn(`${logPrefix} 源目录路径: ${sourcePath}`)
console.warn(`${logPrefix} 如需使用本地原生插件,请在项目根目录创建 nativeplugins 目录`)
console.warn(`${logPrefix} 并按照官方文档放入原生插件文件`)
console.warn(`${logPrefix} 参考: https://uniapp.dcloud.net.cn/plugin/native-plugin.html`)
}
return
}
// 检查源目录是否为空
// 如果目录存在但为空,也跳过复制操作
const sourceFiles = await fs.readdir(sourcePath)
if (sourceFiles.length === 0) {
if (verbose) {
console.warn(`${logPrefix} 源目录为空,跳过复制操作`)
console.warn(`${logPrefix} 源目录路径: ${sourcePath}`)
console.warn(`${logPrefix} 请在 nativeplugins 目录中放入原生插件文件`)
}
return
}
// 确保目标目录及其父目录存在
await fs.ensureDir(targetPath)
if (verbose) {
console.log(`${logPrefix} 开始复制 UniApp 本地原生插件...`)
console.log(`${logPrefix} 源目录: ${sourcePath}`)
console.log(`${logPrefix} 目标目录: ${targetPath}`)
console.log(`${logPrefix} 构建模式: ${buildMode}`)
console.log(`${logPrefix} 目标平台: ${platform}`)
console.log(`${logPrefix} 发现 ${sourceFiles.length} 个原生插件文件/目录`)
}
// 执行文件复制操作
// 将整个 nativeplugins 目录复制到构建输出目录
await fs.copy(sourcePath, targetPath, {
overwrite: true, // 覆盖已存在的文件,确保使用最新版本
errorOnExist: false, // 如果目标文件存在不报错
preserveTimestamps: true, // 保持文件的时间戳
})
if (verbose) {
console.log(`${logPrefix} ✅ UniApp 本地原生插件复制完成`)
console.log(`${logPrefix} 已成功复制 ${sourceFiles.length} 个文件/目录到构建目录`)
console.log(`${logPrefix} 原生插件现在可以在 App 中正常使用`)
}
}
catch (error) {
console.error(`${config.logPrefix} ❌ 复制 UniApp 本地原生插件失败:`, error)
console.error(`${config.logPrefix} 错误详情:`, error instanceof Error ? error.message : String(error))
console.error(`${config.logPrefix} 请检查源目录权限和磁盘空间`)
// 不抛出错误,避免影响整个构建过程,但会记录详细的错误信息
}
},
}
}
/**
* 创建 UniApp 本地原生插件资源复制插件的便捷函数
*
* 这是一个便捷的工厂函数,用于快速创建插件实例
* 特别适用于在 vite.config.ts 中进行条件性插件配置
*
* 使用示例:
* ```typescript
* // 在 vite.config.ts 中
* plugins: [
* // 仅在 app 平台且启用时生效
* UNI_PLATFORM === 'app'
* ? createCopyNativeResourcesPlugin(
* VITE_COPY_NATIVE_RES_ENABLE === 'true',
* { verbose: mode === 'development' }
* )
* : null,
* ]
* ```
*
* @param enable 是否启用插件,通常通过环境变量控制
* @param options 其他配置选项,不包含 enable 属性
* @returns Vite 插件对象
*/
export function createCopyNativeResourcesPlugin(
enable: boolean = true,
options: Omit<CopyNativeResourcesOptions, 'enable'> = {},
): Plugin {
return copyNativeResources({ enable, ...options })
}