叭遭段 发表于 2025-12-22 15:40:01

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

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

然后主包我想用都写在这个分包中组件,其他分包共用的组件也写在这个分包中相当'共享分包'.
根据已有知识配置当前页面,
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: '',
targetPlatform: 'mp-weixin',
}

/**
* 页面配置同步插件
*
* 功能说明:
* 1. 解决微信小程序 componentPlaceholder 配置问题
* 2. 自动将 pages.json 中的 componentPlaceholder 配置同步到对应页面的 .json 文件
* 3. 支持主包页面和分包页面
* 4. 智能合并现有配置,避免覆盖其他配置
*
* 使用场景:
* - 使用 @uni-helper/vite-plugin-uni-pages 插件
* - 在页面中使用 definePage({ componentPlaceholder: {...} })
* - 需要微信小程序平台支持自定义组件的占位配置
*
* 工作原理:
* 1. 在构建完成后读取 dist///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
          // 将 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
          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 })


来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

呼延冰枫 发表于 2026-1-5 19:42:29

谢谢分享,试用一下

跟尴 发表于 2026-1-13 20:42:21

这个好,看起来很实用

铝缉惹 发表于 2026-1-13 22:01:51

谢谢楼主提供!

氛疵 发表于 2026-1-14 11:49:43

yyds。多谢分享

山芷兰 发表于 2026-1-14 20:47:52

不错,里面软件多更新就更好了

能杜孱 发表于 2026-1-15 23:30:42

过来提前占个楼

慷规扣 发表于 2026-1-17 06:21:09

新版吗?好像是停更了吧。

村亢 发表于 2026-1-17 20:15:00

过来提前占个楼

甘子萱 发表于 2026-1-22 21:03:15

不错,里面软件多更新就更好了

肇默步 发表于 2026-1-25 09:22:40

谢谢分享,试用一下

玛凶 发表于 2026-2-8 01:34:29

新版吗?好像是停更了吧。

司寇涵涵 发表于 2026-2-10 03:52:01

这个有用。

齐娅晶 发表于 2026-2-10 07:46:22

感谢分享,学习下。

洪势 发表于 2026-2-10 12:32:48

谢谢楼主提供!

扎先 发表于 2026-2-10 18:24:32

新版吗?好像是停更了吧。

凌彦慧 发表于 2026-2-11 06:02:11

谢谢分享,辛苦了

数察啜 发表于 2026-2-12 08:50:37

前排留名,哈哈哈

庾签 发表于 2026-2-20 04:10:20

不错,里面软件多更新就更好了

揿纰潦 发表于 2026-2-28 12:28:13

热心回复!
页: [1] 2
查看完整版本: 使用@uni-helper/vite-plugin-uni-pages生成小程序主包使用不了分包组件问题