使用@uni-helper/vite-plugin-uni-pages生成小程序主包使用不了分包组件问题

uniapp项目(vue3+ts+vite+unocss)
前提: 开发新项目为了主包不超包;我把一个分包(shared)easycom了,

82a02bec-f193-4c88-bf2b-5979a0c7ace8

然后主包我想用都写在这个分包中组件,其他分包共用的组件也写在这个分包中相当'共享分包'.
根据已有知识配置当前页面,

definePage({
  style: {
    navigationBarTitleText: '',
  },
  // ✅ 在这里配置微信小程序特有的选项
  componentPlaceholder: {
    'shared-my-info': 'view',
    'shared-my-details': 'view',
    'shared-my-calendar': 'view',
    'shared-my-activities': 'view',
  },
})

微信小程序生成了page.json我的引用的页面componentPlaceholder,到这没问题;

问题: 对应页面的json文件没有生成对应代码

componentPlaceholder: {
    'shared-my-info': 'view',
    'shared-my-details': 'view',
    'shared-my-calendar': 'view',
    'shared-my-activities': 'view',
  },

期望生成但是没有,最后我写了vite方法处理生成的json文件如果有引用shared的组件让其生成上面的配置.
vite文件如下,如果需要的话可以拿走,注意我自动生成的pages.json.js如果你是pages.json记得改

import type { Plugin } from 'vite'
import path from 'node:path'
import process from 'node:process'
import fs from 'fs-extra'

/**
 * 页面配置同步插件配置接口
 *
 * 用于解决微信小程序 componentPlaceholder 需要同时配置在 pages.json 和页面 .json 文件的问题
 * 自动将 pages.json 中的 componentPlaceholder 配置同步到对应的页面 .json 文件中
 */
export interface SyncPageConfigOptions {
  /** 是否启用插件 */
  enable?: boolean
  /**
   * 是否显示详细日志,便于调试和监控同步过程
   */
  verbose?: boolean
  /** 自定义日志前缀,用于区分不同插件的日志输出 */
  logPrefix?: string
  /**
   * 目标平台,默认为 'mp-weixin'
   * 可以扩展到其他小程序平台如 'mp-alipay', 'mp-baidu' 等
   */
  targetPlatform?: string
}

/**
 * 默认配置
 */
const DEFAULT_OPTIONS: Required<SyncPageConfigOptions> = {
  enable: true,
  verbose: true,
  logPrefix: '[sync-page-config]',
  targetPlatform: 'mp-weixin',
}

/**
 * 页面配置同步插件
 *
 * 功能说明:
 * 1. 解决微信小程序 componentPlaceholder 配置问题
 * 2. 自动将 pages.json 中的 componentPlaceholder 配置同步到对应页面的 .json 文件
 * 3. 支持主包页面和分包页面
 * 4. 智能合并现有配置,避免覆盖其他配置
 *
 * 使用场景:
 * - 使用 @uni-helper/vite-plugin-uni-pages 插件
 * - 在页面中使用 definePage({ componentPlaceholder: {...} })
 * - 需要微信小程序平台支持自定义组件的占位配置
 *
 * 工作原理:
 * 1. 在构建完成后读取 dist/[mode]/[platform]/pages.json
 * 2. 遍历所有页面配置,查找包含 componentPlaceholder 的页面
 * 3. 将 componentPlaceholder 配置同步到对应的页面 .json 文件
 * 4. 如果页面 .json 文件不存在,则创建新文件
 * 5. 如果已存在,则智能合并配置
 */
export function syncPageConfig(options: SyncPageConfigOptions = {}): Plugin {
  const config = { ...DEFAULT_OPTIONS, ...options }

  // 如果插件被禁用,返回一个空插件
  if (!config.enable) {
    return {
      name: 'sync-page-config-disabled',
      apply: 'build',
      writeBundle() {
        // 插件已禁用,不执行任何操作
      },
    }
  }

  return {
    name: 'sync-page-config',
    apply: 'build', // 只在构建时应用
    enforce: 'post', // 在其他插件执行完毕后执行

    async writeBundle() {
      const { verbose, logPrefix, targetPlatform } = config

      try {
        // 获取项目根目录路径
        const projectRoot = process.cwd()

        // 构建模式:'build' (生产环境) 或 'dev' (开发环境)
        const buildMode = process.env.NODE_ENV === 'production' ? 'build' : 'dev'
        const platform = process.env.UNI_PLATFORM || targetPlatform

        // 只处理目标平台(默认微信小程序)
        if (platform !== targetPlatform) {
          // if (verbose) {
          //   console.log(`${logPrefix} 当前平台 ${platform} 不是目标平台 ${targetPlatform},跳过同步`)
          // }
          return
        }

        // 构建 pages.json.js 文件路径 - 微信小程序生成的是 pages.json.js
        const pagesJsonPath = path.resolve(
          projectRoot,
          'dist',
          buildMode,
          platform,
          'pages.json.js',
        )

        // 检查 pages.json.js 是否存在
        const pagesJsonExists = await fs.pathExists(pagesJsonPath)
        if (!pagesJsonExists) {
          if (verbose) {
            console.warn(`${logPrefix} pages.json.js 不存在,跳过同步操作`)
            console.warn(`${logPrefix} 文件路径: ${pagesJsonPath}`)
          }
          return
        }

        // if (verbose) {
        //   console.log(`${logPrefix} 开始同步页面配置...`)
        //   console.log(`${logPrefix} 构建模式: ${buildMode}`)
        //   console.log(`${logPrefix} 目标平台: ${platform}`)
        //   console.log(`${logPrefix} pages.json.js 路径: ${pagesJsonPath}`)
        // }

        // 直接读取并解析 JavaScript 文件
        const fileContent = await fs.readFile(pagesJsonPath, 'utf8')
        const pagesJson = parseJavaScriptConfigFile(fileContent, verbose, logPrefix)

        if (!pagesJson) {
          console.error(`${logPrefix} ❌ 解析配置文件失败,无法继续`)
          return
        }

        // 调试:显示解析结果
        if (verbose) {
          // console.log(`${logPrefix} 解析成功,找到配置:`)
          // console.log(`${logPrefix} - 主包页面数: ${pagesJson.pages?.length || 0}`)
          // console.log(`${logPrefix} - 分包数量: ${pagesJson.subPackages?.length || 0}`)

          // 查找 me 页面
          const mePage = pagesJson.pages?.find((p: any) => p.path === 'pages/me/me')
          // if (mePage) {
          //   console.log(`${logPrefix} - 找到 me 页面: ${mePage.path}`)
          //   if (mePage.componentPlaceholder) {
          //     console.log(`${logPrefix} - me 页面有 componentPlaceholder:`, Object.keys(mePage.componentPlaceholder))
          //   }
          // }
        }

        let totalProcessed = 0

        // 处理主包页面
        if (pagesJson.pages && Array.isArray(pagesJson.pages)) {
          totalProcessed += await processPages(
            pagesJson.pages,
            path.resolve(projectRoot, 'dist', buildMode, platform),
            '',
            verbose,
            logPrefix,
          )
        }

        // 处理分包页面
        if (pagesJson.subPackages && Array.isArray(pagesJson.subPackages)) {
          for (const subPackage of pagesJson.subPackages) {
            if (subPackage.pages && subPackage.root) {
              const subPackageRoot = path.resolve(
                projectRoot,
                'dist',
                buildMode,
                platform,
                subPackage.root,
              )

              totalProcessed += await processPages(
                subPackage.pages,
                subPackageRoot,
                subPackage.root,
                verbose,
                logPrefix,
              )
            }
          }
        }

        // if (verbose) {
        //   console.log(`${logPrefix} ✅ 页面配置同步完成`)
        //   console.log(`${logPrefix} 共处理 ${totalProcessed} 个页面的 componentPlaceholder 配置`)
        // }
      }
      catch (error) {
        console.error(`${logPrefix} ❌ 同步页面配置失败:`, error)
        console.error(`${logPrefix} 错误详情:`, error instanceof Error ? error.message : String(error))
        console.error(`${logPrefix} 堆栈:`, error instanceof Error ? error.stack : '无堆栈信息')
        // 不抛出错误,避免影响整个构建过程
      }
    },
  }
}

/**
 * 解析 JavaScript 配置文件
 * 专门处理微信小程序的 pages.json.js 格式
 */
function parseJavaScriptConfigFile(
  jsContent: string,
  verbose: boolean,
  logPrefix: string,
): any {
  try {
    // 根据你提供的文件格式,这是一个 CommonJS 模块
    // 我们需要执行这个 JavaScript 代码来获取 exports

    // 方法1:使用 Function 构造函数创建一个安全的执行环境
    const moduleCode = `
      const exports = {};
      const module = { exports };
      ${jsContent};
      return module.exports;
    `

    const getModuleExports = new Function(moduleCode)
    const result = getModuleExports()

    // if (verbose) {
    //   console.log(`${logPrefix} 成功解析 JavaScript 配置文件`)
    // }

    return result
  }
  catch (error) {
    console.error(`${logPrefix} ❌ 解析 JavaScript 配置文件失败:`, error)

    // 方法2:尝试简单的正则解析(备选方案)
    try {
      const result: any = {}

      // 提取 pages 数组
      const pagesMatch = jsContent.match(/const pages = (\[[\s\S]*?\]);/)
      if (pagesMatch) {
        try {
          const pagesCode = pagesMatch[1]
          // 将 JavaScript 数组转换为 JSON
          const jsonStr = pagesCode
            .replace(/(['"])?(\w+)(['"])?\s*:/g, '"$2":')
            .replace(/'/g, '"')
            .replace(/,\s*\]/g, ']') // 移除尾随逗号

          result.pages = JSON.parse(jsonStr)
          // if (verbose) {
          //   console.log(`${logPrefix} 使用正则成功解析 pages`)
          // }
        }
        catch (e) {
          console.warn(`${logPrefix} ⚠️ 正则解析 pages 失败:`, e)
        }
      }

      // 提取 subPackages 数组
      const subPackagesMatch = jsContent.match(/const subPackages = (\[[\s\S]*?\]);/)
      if (subPackagesMatch) {
        try {
          const subPackagesCode = subPackagesMatch[1]
          const jsonStr = subPackagesCode
            .replace(/(['"])?(\w+)(['"])?\s*:/g, '"$2":')
            .replace(/'/g, '"')
            .replace(/,\s*\]/g, ']')

          result.subPackages = JSON.parse(jsonStr)
          // if (verbose) {
          //   console.log(`${logPrefix} 使用正则成功解析 subPackages`)
          // }
        }
        catch (e) {
          console.warn(`${logPrefix} ⚠️ 正则解析 subPackages 失败:`, e)
        }
      }

      if (result.pages || result.subPackages) {
        return result
      }

      throw new Error('两种解析方法都失败了')
    }
    catch (fallbackError) {
      console.error(`${logPrefix} ❌ 备用解析方案也失败:`, fallbackError)
      return null
    }
  }
}

/**
 * 处理页面数组
 */
async function processPages(
  pages: any[],
  baseDir: string,
  rootPath: string,
  verbose: boolean,
  logPrefix: string,
): Promise<number> {
  let processedCount = 0

  // 安全检查:确保 pages 是数组
  if (!Array.isArray(pages)) {
    console.warn(`${logPrefix} ⚠️ pages 不是数组,跳过处理`)
    return 0
  }

  for (const page of pages) {
    // 安全检查:确保 page 是对象
    if (!page || typeof page !== 'object') {
      console.warn(`${logPrefix} ⚠️ 页面配置不是对象,跳过`)
      continue
    }

    // 检查是否有 componentPlaceholder 配置
    const hasPlaceholder = page.componentPlaceholder
      && typeof page.componentPlaceholder === 'object'
      && Object.keys(page.componentPlaceholder).length > 0

    if (hasPlaceholder) {
      try {
        // 获取页面路径,确保移除 .vue 扩展名
        let pagePath = page.path
        if (pagePath && typeof pagePath === 'string' && pagePath.endsWith('.vue')) {
          pagePath = pagePath.slice(0, -4) // 移除 .vue
        }

        if (!pagePath || typeof pagePath !== 'string') {
          console.warn(`${logPrefix} ⚠️ 页面路径无效: ${pagePath}`)
          continue
        }

        // 构建目标 .json 文件路径
        const jsonFilePath = path.join(baseDir, `${pagePath}.json`)

        // 确保目录存在
        await fs.ensureDir(path.dirname(jsonFilePath))

        // 读取或创建页面配置
        let pageConfig: any = {}
        if (await fs.pathExists(jsonFilePath)) {
          try {
            const existingContent = await fs.readFile(jsonFilePath, 'utf8')
            pageConfig = JSON.parse(existingContent)
          }
          catch (readError) {
            if (verbose) {
              console.warn(`${logPrefix} ⚠️ 无法读取现有配置文件 ${jsonFilePath},将创建新配置`)
            }
          }
        }

        // 确保必要的字段存在
        if (!pageConfig.navigationBarTitleText && page.style?.navigationBarTitleText) {
          pageConfig.navigationBarTitleText = page.style.navigationBarTitleText
        }

        // 确保 usingComponents 存在
        if (!pageConfig.usingComponents) {
          pageConfig.usingComponents = {}
        }

        // 合并 componentPlaceholder
        const placeholder = page.componentPlaceholder || {}
        pageConfig.componentPlaceholder = {
          ...(pageConfig.componentPlaceholder || {}),
          ...placeholder,
        }

        // 写入文件
        await fs.writeFile(jsonFilePath, JSON.stringify(pageConfig, null, 2))
        processedCount++

        if (verbose) {
          const logRootPrefix = rootPath ? `[${rootPath}] ` : ''
          const placeholderCount = Object.keys(placeholder).length
          // console.log(`${logPrefix} ${logRootPrefix}✅ 已同步: ${pagePath} → ${placeholderCount} 个占位组件`)
        }
      }
      catch (pageError) {
        console.error(`${logPrefix} ❌ 处理页面 ${page?.path || '未知页面'} 失败:`, pageError)
      }
    }
    else if (verbose) {
      // 调试信息:显示没有 componentPlaceholder 的页面
      const logRootPrefix = rootPath ? `[${rootPath}] ` : ''
      // console.log(`${logPrefix} ${logRootPrefix}跳过: ${page.path || '未知页面'} (无 componentPlaceholder)`)
    }
  }

  return processedCount
}

/**
 * 创建页面配置同步插件的便捷函数
 *
 * 这是一个便捷的工厂函数,用于快速创建插件实例
 * 特别适用于在 vite.config.ts 中进行条件性插件配置
 *
 * 使用示例:
 * ```typescript
 * // 在 vite.config.ts 中
 * plugins: [
 *   // 仅在微信小程序平台启用
 *   createSyncPageConfigPlugin(
 *     UNI_PLATFORM === 'mp-weixin',
 *     { verbose: mode === 'development' }
 *   ),
 * ]
 * ```
 */
export function createSyncPageConfigPlugin(
  enable: boolean = true,
  options: Omit<SyncPageConfigOptions, 'enable'> = {},
): Plugin {
  return syncPageConfig({ enable, ...options })
}

 

posted @ 2025-12-22 15:10  给第一张敬个礼  阅读(121)  评论(0)    收藏  举报