@fchc8/vite-plugin-multi-page
Version:
A powerful Vite plugin for building multi-page applications with smart file routing and multi-strategy builds
1 lines • 67.7 kB
Source Map (JSON)
{"version":3,"sources":["../src/index.ts","../src/dev-server.ts","../src/file-filter.ts","../src/utils.ts","../src/page-config.ts","../src/build-config.ts","../src/config-loader.ts","../src/defaults.ts","../src/types.ts"],"sourcesContent":["import type { Plugin } from 'vite';\nimport { mergeConfig } from 'vite';\nimport { setupDevMiddleware } from './dev-server';\nimport { generateBuildConfig } from './build-config';\nimport { loadUserConfig, hasCustomConfig } from './config-loader';\nimport { mergeWithDefaults } from './defaults';\nimport type { Options, ConfigTransformFunction } from './types';\nimport * as fs from 'fs';\n\n// 导出类型和工具函数\nexport { defineConfig, defineConfigTransform } from './types';\nexport type {\n ConfigFunction,\n ConfigTransformFunction,\n PluginContext,\n PageContext,\n PageConfig,\n} from './types';\n\nexport function viteMultiPage(transform?: ConfigTransformFunction): Plugin {\n let resolvedOptions: Options;\n const tempFiles: string[] = [];\n let log: (...args: any[]) => void = () => {}; // 默认为空函数\n\n return {\n name: 'vite-multi-page',\n\n async configResolved(config) {\n // 加载用户配置文件(如果存在)\n let userConfig: Options | null = null;\n\n if (hasCustomConfig()) {\n userConfig = await loadUserConfig({\n mode: config.command === 'serve' ? 'development' : 'production',\n command: config.command,\n isCLI: false,\n });\n }\n\n // 合并用户配置和默认配置\n const mergedConfig = mergeWithDefaults(userConfig);\n\n // 应用配置变换函数(如果提供)\n resolvedOptions = transform\n ? transform(mergedConfig, {\n mode: config.command === 'serve' ? 'development' : 'production',\n command: config.command,\n isCLI: false,\n })\n : mergedConfig;\n\n // 设置debug日志\n const debug = resolvedOptions.debug ?? false;\n log = debug ? console.log.bind(console, '[vite-multi-page]') : () => {};\n\n log('Vite配置已解析, 使用配置:', {\n strategies: Object.keys(resolvedOptions.strategies || {}),\n entry: resolvedOptions.entry,\n });\n },\n\n async config(config, { command }) {\n // 处理开发模式下的策略参数\n if (command === 'serve') {\n // 检查命令行参数中的策略设置\n const args = process.argv;\n\n // 查找 --strategy=value 格式的参数\n const strategyArg = args.find(arg => arg.startsWith('--strategy='));\n if (strategyArg) {\n const strategy = strategyArg.split('=')[1];\n if (strategy) {\n process.env.VITE_MULTI_PAGE_STRATEGY = strategy;\n }\n }\n // 查找 --strategy value 格式的参数\n else {\n const strategyIndex = args.findIndex(arg => arg === '--strategy');\n if (strategyIndex !== -1 && strategyIndex + 1 < args.length) {\n const strategy = args[strategyIndex + 1];\n process.env.VITE_MULTI_PAGE_STRATEGY = strategy;\n }\n }\n\n // 确保有默认策略\n if (!process.env.VITE_MULTI_PAGE_STRATEGY) {\n process.env.VITE_MULTI_PAGE_STRATEGY = 'default';\n }\n }\n if (command === 'build') {\n // 在config钩子中临时加载配置,因为configResolved还没运行\n if (!resolvedOptions) {\n // 加载用户配置文件(如果存在)\n let userConfig: Options | null = null;\n\n if (hasCustomConfig()) {\n userConfig = await loadUserConfig({\n mode: 'production',\n command: 'build',\n isCLI: false,\n });\n }\n\n // 合并用户配置和默认配置\n const mergedConfig = mergeWithDefaults(userConfig);\n\n // 应用配置变换函数(如果提供)\n resolvedOptions = transform\n ? transform(mergedConfig, {\n mode: 'production',\n command: 'build',\n isCLI: false,\n })\n : mergedConfig;\n const debug = resolvedOptions.debug ?? false;\n log = debug ? console.log.bind(console, '[vite-multi-page]') : () => {};\n }\n\n log('配置构建模式');\n\n // 策略构建模式:生成构建配置\n const forceBuildStrategy = process.env.VITE_MULTI_PAGE_STRATEGY;\n const buildConfigs = generateBuildConfig({\n entry: resolvedOptions.entry || 'src/pages/**/*.{ts,js}',\n exclude: resolvedOptions.exclude || [],\n template: resolvedOptions.template || 'index.html',\n placeholder: resolvedOptions.placeholder || '{{ENTRY_FILE}}',\n strategies: resolvedOptions.strategies || {},\n pageConfigs: resolvedOptions.pageConfigs || {},\n forceBuildStrategy,\n });\n\n // 应用构建配置中的策略(如果有forceBuildStrategy,buildConfigs只会包含该策略)\n const targetStrategy = Object.keys(buildConfigs)[0];\n\n if (targetStrategy && buildConfigs[targetStrategy]) {\n log(`应用构建策略: ${targetStrategy}`);\n const strategyConfig = buildConfigs[targetStrategy];\n\n // 使用Vite的mergeConfig进行智能深度合并\n const mergedConfig = mergeConfig(config, strategyConfig);\n\n // 将合并结果复制回config对象\n Object.assign(config, mergedConfig);\n\n log(`已应用策略 \"${targetStrategy}\" 的配置:`, {\n build: !!strategyConfig.build,\n define: !!strategyConfig.define,\n plugins: strategyConfig.plugins?.length || 0,\n });\n } else {\n log('未找到可用的构建策略,使用默认配置');\n\n throw new Error(\n '❌ 构建失败: 未找到任何构建策略\\n\\n' +\n '可能的原因:\\n' +\n ' 1. 配置文件返回空对象 {}\\n' +\n ' 2. 未找到匹配的入口文件\\n' +\n ' 3. 模板文件不存在\\n' +\n ' 4. 未配置 strategies 对象\\n\\n' +\n '最小配置示例:\\n' +\n 'export default () => ({\\n' +\n ' entry: \"src/pages/**/*.{ts,js}\",\\n' +\n ' template: \"index.html\",\\n' +\n ' strategies: {\\n' +\n ' default: {}\\n' +\n ' }\\n' +\n '});'\n );\n }\n }\n },\n\n configureServer(server) {\n if (server.config.command === 'serve') {\n log('配置开发服务器');\n\n // 处理开发模式下的策略参数\n // 从环境变量中获取策略,默认为 default\n const devStrategy = process.env.VITE_MULTI_PAGE_STRATEGY || 'default';\n\n log(`开发模式策略: ${devStrategy}`);\n\n setupDevMiddleware(\n server,\n {\n entry: resolvedOptions.entry || 'src/pages/**/*.{ts,js}',\n exclude: resolvedOptions.exclude || [],\n template: resolvedOptions.template || 'index.html',\n placeholder: resolvedOptions.placeholder || '{{ENTRY_FILE}}',\n strategies: resolvedOptions.strategies || {},\n pageConfigs: resolvedOptions.pageConfigs || {},\n devStrategy: devStrategy, // 传递策略给开发服务器\n },\n log\n );\n }\n },\n\n writeBundle() {\n // 构建完成,无需额外处理\n // 每个策略已经直接输出到对应的目录\n },\n\n buildEnd() {\n // 清理临时文件\n if (tempFiles.length > 0) {\n log(`清理 ${tempFiles.length} 个临时文件`);\n tempFiles.forEach(file => {\n try {\n if (fs.existsSync(file)) {\n fs.unlinkSync(file);\n log(`删除临时文件: ${file}`);\n }\n } catch (error) {\n log(`删除临时文件失败: ${file}`, error);\n }\n });\n tempFiles.length = 0;\n }\n },\n };\n}\n\nexport default viteMultiPage;\nexport type { Options } from './types';\nexport {\n generateBuildConfig,\n getAvailableStrategies,\n getViteOutputDirectory,\n cleanViteOutputDirectory,\n} from './build-config';\nexport { mergeWithDefaults } from './defaults';\n","import type { ViteDevServer } from 'vite';\nimport * as path from 'node:path';\nimport * as fs from 'node:fs';\nimport { glob } from 'glob';\nimport { filterEntryFiles } from './file-filter';\nimport { escapeRegExp } from './utils';\nimport { DevServerOptions, PageConfigContext } from './types';\nimport { getPageConfig } from './page-config';\n\nexport function configureDevServer(\n server: ViteDevServer,\n options: DevServerOptions,\n log: (...args: any[]) => void\n) {\n try {\n const allFiles = glob.sync(options.entry, { cwd: process.cwd() });\n let entryFiles = filterEntryFiles(allFiles, options.entry, options.exclude, log);\n\n if (entryFiles.length === 0) {\n log('警告: 未找到匹配的入口文件');\n return;\n }\n\n // 获取指定的策略,优先使用开发模式传入的策略\n const cliStrategy =\n options.devStrategy ||\n (((server.config as any).__cliStrategy || (server.config as any).strategy) as\n | string\n | undefined);\n\n // 如果指定了策略,则只显示该策略下的页面或没有指定策略的默认页面\n if (cliStrategy) {\n log(`开发服务器使用指定的策略: ${cliStrategy}`);\n\n // 过滤入口文件,只保留匹配策略的页面\n entryFiles = entryFiles.filter(file => {\n // 动态获取页面策略\n const pageContext = {\n pageName: file.name,\n filePath: file.file,\n relativePath: path.relative(process.cwd(), file.file),\n strategy: undefined,\n isMatched: false,\n } as PageConfigContext;\n\n const pageConfig = getPageConfig(options.pageConfigs, pageContext, log);\n const pageStrategy = pageConfig?.strategy || 'default';\n\n // 在指定策略为default时,包含所有没有指定策略的页面\n if (cliStrategy === 'default') {\n return pageStrategy === 'default';\n }\n\n // 其他策略,只包含匹配的页面\n return pageStrategy === cliStrategy;\n });\n\n log(`策略 \"${cliStrategy}\" 下可用的页面: ${entryFiles.map(f => f.name).join(', ') || '无'}`);\n }\n\n log('开发服务器应用的入口文件:', entryFiles);\n\n // 修改中间件来处理HTML请求\n server.middlewares.use(async (req, res, next) => {\n try {\n const url = req.url || '';\n const pathWithoutQuery = url.split('?')[0];\n\n // 处理根路径请求 - 显示所有页面的索引\n if (pathWithoutQuery === '/') {\n const indexHtml = generateIndexHtml(entryFiles, options, log);\n res.statusCode = 200;\n res.setHeader('Content-Type', 'text/html');\n res.end(indexHtml);\n return;\n }\n\n // 跳过明显的静态资源请求\n if (\n pathWithoutQuery.match(/\\.(js|css|png|jpg|jpeg|gif|svg|ico|woff|woff2|ttf|eot|map)$/) &&\n !pathWithoutQuery.endsWith('.html')\n ) {\n return next();\n }\n\n // 跳过以@开头的特殊路径(如Vite的特殊路径)\n if (pathWithoutQuery.startsWith('/@')) {\n return next();\n }\n\n // 跳过 __vite_ping 和其他 Vite 内部路径\n if (pathWithoutQuery.includes('__vite') || pathWithoutQuery.startsWith('/node_modules')) {\n return next();\n }\n\n // 提取页面名称,支持 history 路由\n let pageName = '';\n\n // 1. 处理带 .html 后缀的请求\n if (pathWithoutQuery.endsWith('.html')) {\n pageName = path.basename(pathWithoutQuery, '.html');\n }\n // 2. 处理精确匹配的页面路径,如 /mobile\n else if (pathWithoutQuery.startsWith('/')) {\n const cleanPath = pathWithoutQuery.substring(1); // 移除开头的斜杠\n\n // 首先尝试精确匹配\n if (entryFiles.find(file => file.name === cleanPath)) {\n pageName = cleanPath;\n }\n // 然后尝试 history 路由匹配,如 /home/login -> home\n else {\n const segments = cleanPath.split('/');\n if (segments.length > 1) {\n const possiblePageName = segments[0];\n if (entryFiles.find(file => file.name === possiblePageName)) {\n pageName = possiblePageName;\n log(`History 路由匹配: ${pathWithoutQuery} -> ${possiblePageName}`);\n }\n }\n }\n }\n\n if (!pageName) {\n return next();\n }\n\n const matchedFile = entryFiles.find(file => file.name === pageName);\n\n if (!matchedFile) {\n return next();\n }\n\n return servePageHtml(res, matchedFile, options, log);\n } catch (error) {\n log(`开发服务器处理请求失败: ${error}`);\n next(error);\n }\n });\n\n log('开发服务器配置完成');\n } catch (error) {\n log(`配置开发服务器失败: ${error}`);\n throw error;\n }\n}\n\n// 提取页面HTML服务逻辑\nfunction servePageHtml(\n res: any,\n matchedFile: { name: string; file: string },\n options: DevServerOptions,\n log: (...args: any[]) => void\n) {\n // 获取页面配置\n const pageContext = {\n pageName: matchedFile.name,\n filePath: matchedFile.file,\n relativePath: path.relative(process.cwd(), matchedFile.file),\n strategy: undefined,\n isMatched: false,\n } as PageConfigContext;\n\n const pageConfig = getPageConfig(options.pageConfigs, pageContext, log);\n\n // 应用配置策略\n if (pageConfig?.strategy) {\n pageContext.strategy = pageConfig.strategy;\n } else if (options.appliedStrategies?.has(matchedFile.name)) {\n // 使用缓存的策略信息\n const strategyName = options.appliedStrategies.get(matchedFile.name);\n if (strategyName) {\n pageContext.strategy = strategyName;\n }\n }\n\n // 获取模板文件路径\n // 首先检查是否有页面特定的模板(例如mobile.html对应mobile页面)\n let templatePath = '';\n\n // 尝试以页面名称查找匹配的模板\n const pageSpecificTemplate = path.resolve(process.cwd(), `${matchedFile.name}.html`);\n if (fs.existsSync(pageSpecificTemplate)) {\n templatePath = pageSpecificTemplate;\n }\n // 然后尝试使用页面配置中指定的模板\n else if (pageConfig?.template) {\n templatePath = path.resolve(process.cwd(), pageConfig.template);\n }\n // 最后使用默认模板\n else {\n templatePath = path.resolve(process.cwd(), options.template);\n }\n\n if (!fs.existsSync(templatePath)) {\n res.statusCode = 404;\n res.end('Template not found');\n return;\n }\n\n // 读取并修改模板\n let html = fs.readFileSync(templatePath, 'utf-8');\n\n // 检查模板中是否包含占位符\n const containsPlaceholder = html.includes(options.placeholder);\n\n // 替换占位符为入口文件路径\n if (containsPlaceholder) {\n const originalHtml = html;\n\n // 方式1: 直接字符串替换\n html = html.split(options.placeholder).join(`/${matchedFile.file}`);\n\n // 检查替换结果\n if (html === originalHtml) {\n // 方式2: 正则表达式替换\n const escapedPlaceholder = escapeRegExp(options.placeholder);\n const placeholderRegex = new RegExp(escapedPlaceholder, 'g');\n html = originalHtml.replace(placeholderRegex, `/${matchedFile.file}`);\n\n // 检查替换结果\n if (html === originalHtml) {\n // 方式3: 硬编码替换具体的占位符格式\n html = originalHtml.replace(/\\{\\{ENTRY_FILE\\}\\}/g, `/${matchedFile.file}`);\n }\n }\n }\n\n // 添加页面级define变量\n if (pageConfig?.define) {\n const defineScript = Object.entries(pageConfig.define)\n .map(([key, value]) => {\n const stringValue = typeof value === 'string' ? `\"${value}\"` : JSON.stringify(value);\n return `window.${key} = ${stringValue};`;\n })\n .join('\\n');\n\n if (defineScript) {\n // 注入到head标签底部\n html = html.replace(\n /<\\/head>/i,\n `<script type=\"text/javascript\">\\n${defineScript}\\n</script>\\n</head>`\n );\n }\n }\n\n // 发送响应\n res.statusCode = 200;\n res.setHeader('Content-Type', 'text/html');\n res.end(html);\n}\n\n// 为了兼容性,导出setupDevMiddleware作为configureDevServer的别名\nexport const setupDevMiddleware = configureDevServer;\n\n// 生成索引页面HTML\nfunction generateIndexHtml(\n entryFiles: { name: string; file: string }[],\n options: DevServerOptions,\n log: (...args: any[]) => void\n): string {\n try {\n const pageItems = entryFiles\n .map(file => {\n // 获取页面配置和策略\n const pageContext = {\n pageName: file.name,\n filePath: file.file,\n relativePath: path.relative(process.cwd(), file.file),\n strategy: undefined,\n isMatched: false,\n };\n\n const pageConfig = getPageConfig(options.pageConfigs, pageContext, log);\n\n // 确定策略\n let strategy = 'default';\n if (pageConfig?.strategy) {\n strategy = pageConfig.strategy;\n } else if (options.appliedStrategies?.has(file.name)) {\n const strategyName = options.appliedStrategies.get(file.name);\n if (strategyName) {\n strategy = strategyName;\n }\n }\n\n const strategyBadge =\n strategy !== 'default' ? `<span class=\"badge\">${strategy}</span>` : '';\n\n return `\n <div class=\"page-item\">\n <a href=\"${file.name}.html\" class=\"page-link\">\n ${file.name}${strategyBadge}\n </a>\n <div class=\"page-path\">${file.file}</div>\n </div>`;\n })\n .join('');\n\n return `\n <!DOCTYPE html>\n <html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>多页面应用索引</title>\n <style>\n body {\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif;\n line-height: 1.6;\n color: #333;\n max-width: 1200px;\n margin: 0 auto;\n padding: 20px;\n background-color: #f5f5f7;\n }\n h1 {\n font-size: 24px;\n margin-bottom: 20px;\n color: #111;\n }\n .page-list {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));\n gap: 16px;\n }\n .page-item {\n background-color: white;\n border-radius: 8px;\n padding: 16px;\n box-shadow: 0 1px 3px rgba(0,0,0,0.1);\n transition: transform 0.2s, box-shadow 0.2s;\n }\n .page-item:hover {\n transform: translateY(-2px);\n box-shadow: 0 4px 8px rgba(0,0,0,0.1);\n }\n .page-link {\n display: flex;\n align-items: center;\n justify-content: space-between;\n font-size: 18px;\n font-weight: 500;\n color: #0066cc;\n text-decoration: none;\n margin-bottom: 8px;\n }\n .page-path {\n font-size: 14px;\n color: #666;\n word-break: break-all;\n }\n .badge {\n display: inline-block;\n font-size: 12px;\n padding: 2px 8px;\n border-radius: 12px;\n background-color: #e6f2ff;\n color: #0066cc;\n margin-left: 8px;\n }\n .stats {\n margin-bottom: 20px;\n font-size: 14px;\n color: #666;\n }\n </style>\n </head>\n <body>\n <h1>多页面应用索引</h1>\n <div class=\"stats\">\n 找到 ${entryFiles.length} 个页面\n </div>\n <div class=\"page-list\">\n ${pageItems}\n </div>\n </body>\n </html>\n `;\n } catch (error) {\n log(`生成索引页失败: ${error}`);\n return `\n <!DOCTYPE html>\n <html>\n <head>\n <title>错误</title>\n </head>\n <body>\n <h1>生成索引页时发生错误</h1>\n <p>${error}</p>\n </body>\n </html>\n `;\n }\n}\n","import * as path from 'node:path';\nimport type { EntryFile, CandidateFile } from './types';\n\nexport function filterEntryFiles(\n files: string[],\n entry: string,\n exclude: string[],\n _log: (...args: any[]) => void\n): EntryFile[] {\n const result: EntryFile[] = [];\n const nameToFile = new Map<string, { file: string; priority: number }>();\n\n // 从entry模式中提取基础目录\n let basePattern = entry.replace(/\\/\\*.*$/, ''); // 去掉glob部分\n // 如果基础模式为空或不合理,使用默认处理\n if (!basePattern || basePattern === entry) {\n basePattern = path.dirname(entry.split('*')[0]);\n }\n const candidateFiles: CandidateFile[] = [];\n\n for (const file of files) {\n if (exclude.includes(file)) {\n continue;\n }\n\n // 统一使用正斜杠处理路径,确保Windows兼容性\n const normalizedFile = file.replace(/\\\\/g, '/');\n const normalizedBasePattern = basePattern.replace(/\\\\/g, '/');\n\n const relativePath = path.posix.relative(normalizedBasePattern, normalizedFile);\n const pathParts = relativePath.split('/'); // 使用正斜杠分割\n\n if (pathParts.length === 1) {\n // 第一级文件:src/pages/about.js -> /about.html\n const fileName = pathParts[0];\n const name = path.posix.basename(fileName, path.posix.extname(fileName));\n candidateFiles.push({ name, file, priority: 1 });\n } else if (pathParts.length >= 2) {\n // 目录下的文件\n const fileName = path.posix.basename(normalizedFile, path.posix.extname(normalizedFile));\n const dirName = pathParts[0];\n\n if (fileName === 'main') {\n // 目录下的main文件:src/pages/mobile/main.ts -> /mobile.html\n candidateFiles.push({ name: dirName, file, priority: 2 });\n }\n }\n }\n\n // 按照优先级处理冲突:目录优先覆盖文件(优先级2 > 优先级1)\n for (const candidate of candidateFiles) {\n const existing = nameToFile.get(candidate.name);\n\n if (!existing) {\n nameToFile.set(candidate.name, { file: candidate.file, priority: candidate.priority });\n } else {\n if (candidate.priority > existing.priority) {\n nameToFile.set(candidate.name, { file: candidate.file, priority: candidate.priority });\n }\n }\n }\n\n for (const [name, { file }] of nameToFile.entries()) {\n result.push({ name, file });\n }\n\n return result;\n}\n","export function escapeRegExp(string: string): string {\n return string.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\nexport function createLogger(debug: boolean) {\n return (...args: any[]) => {\n if (debug) {\n console.log('[vite-plugin-multi-page]', ...args);\n }\n };\n}\n","import type { PageConfig, PageConfigFunction, PageConfigContext } from './types';\n\n/**\n * 根据页面上下文获取页面配置\n */\nexport function getPageConfig(\n pageConfigs: Record<string, PageConfig> | PageConfigFunction | undefined,\n context: PageConfigContext,\n log: (...args: any[]) => void\n): PageConfig | null {\n if (!pageConfigs) return null;\n\n // 如果是函数,直接调用\n if (typeof pageConfigs === 'function') {\n const result = pageConfigs(context);\n return result;\n }\n\n // 对象配置:支持精确匹配和模式匹配\n for (const [key, config] of Object.entries(pageConfigs)) {\n // 精确匹配页面名称\n if (key === context.pageName) {\n log(`精确匹配页面 ${context.pageName}:`, config);\n return config;\n }\n\n // 模式匹配\n if (config.match) {\n const patterns = Array.isArray(config.match) ? config.match : [config.match];\n const isMatched = patterns.some(\n pattern =>\n simpleMatch(pattern, context.pageName) ||\n simpleMatch(pattern, context.relativePath) ||\n simpleMatch(pattern, context.filePath)\n );\n\n if (isMatched) {\n log(`模式匹配页面 ${context.pageName} (模式: ${config.match}):`, config);\n return { ...config, match: undefined };\n }\n }\n\n // glob 模式匹配页面名称\n if (simpleMatch(key, context.pageName)) {\n log(`Glob匹配页面 ${context.pageName} (模式: ${key}):`, config);\n return config;\n }\n }\n\n return null;\n}\n\n/**\n * 简单的模式匹配函数\n */\nfunction simpleMatch(pattern: string, text: string): boolean {\n const regexPattern = pattern\n .replace(/\\*\\*/g, '__DOUBLE_STAR__')\n .replace(/\\*/g, '[^/]*')\n .replace(/__DOUBLE_STAR__/g, '.*');\n const regex = new RegExp(`^${regexPattern}$`);\n return regex.test(text);\n}\n","import type { UserConfig } from 'vite';\nimport { mergeConfig } from 'vite';\nimport { glob } from 'glob';\nimport * as path from 'node:path';\nimport * as fs from 'node:fs';\nimport { filterEntryFiles } from './file-filter';\nimport { getPageConfig } from './page-config';\nimport type { BuildConfigOptions, PageConfigContext, ConfigStrategy } from './types';\nimport { createLogger } from './utils';\n\n/**\n * 构建时配置生成器\n * 根据策略和页面配置生成多页面构建配置\n */\nexport function generateBuildConfig(options: BuildConfigOptions): Record<string, UserConfig> {\n const {\n entry = 'src/pages/*/main.{ts,js}',\n exclude = [],\n template = 'index.html',\n placeholder = '<!--VITE_MULTI_PAGE_ENTRY-->',\n strategies = {},\n pageConfigs = {},\n forceBuildStrategy,\n } = options;\n\n const log = createLogger(true);\n const buildConfigs: Record<string, UserConfig> = {};\n\n try {\n // 1. 发现所有页面入口文件\n const allFiles = glob.sync(entry, { cwd: process.cwd() });\n const entryFiles = filterEntryFiles(allFiles, entry, exclude, log);\n\n if (entryFiles.length === 0) {\n log('警告: 未找到匹配的入口文件');\n return {};\n }\n\n // 2. 为每个页面分析配置和策略\n const pageStrategies = new Map<string, string>();\n const strategyPages = new Map<string, string[]>();\n\n for (const entryFile of entryFiles) {\n const pageContext = {\n pageName: entryFile.name,\n filePath: entryFile.file,\n relativePath: path.relative(process.cwd(), entryFile.file),\n } as PageConfigContext;\n\n // 获取页面配置\n const pageConfig = getPageConfig(pageConfigs, pageContext, log);\n const strategyName = pageConfig?.strategy || 'default';\n\n pageStrategies.set(entryFile.name, strategyName);\n\n if (!strategyPages.has(strategyName)) {\n strategyPages.set(strategyName, []);\n }\n strategyPages.get(strategyName)?.push(entryFile.name);\n }\n\n log(`📄 发现 ${entryFiles.length} 个页面: ${entryFiles.map(f => f.name).join(', ')}`);\n\n // 3. 如果指定了强制策略,只构建该策略的页面\n if (forceBuildStrategy) {\n const targetPages = strategyPages.get(forceBuildStrategy) || [];\n if (targetPages.length === 0) {\n log(`警告: 策略 \"${forceBuildStrategy}\" 下没有页面`);\n return {};\n }\n\n log(`强制构建策略: ${forceBuildStrategy}, 页面: ${targetPages.join(', ')}`);\n\n const config = generateStrategyConfig(\n forceBuildStrategy,\n targetPages,\n entryFiles,\n strategies[forceBuildStrategy],\n pageConfigs,\n template,\n placeholder,\n log\n );\n\n buildConfigs[forceBuildStrategy] = config;\n return buildConfigs;\n }\n\n // 4. 为每个策略生成构建配置\n for (const [strategyName, pages] of strategyPages) {\n if (pages.length === 0) continue;\n\n // 获取策略配置,如果没有定义则使用空配置(允许默认策略)\n const strategyConfig = strategies[strategyName] || {};\n const config = generateStrategyConfig(\n strategyName,\n pages,\n entryFiles,\n strategyConfig,\n pageConfigs,\n template,\n placeholder,\n log\n );\n\n buildConfigs[strategyName] = config;\n }\n\n // 确保至少有一个构建配置\n if (Object.keys(buildConfigs).length === 0) {\n log('警告: 未生成任何构建配置,创建默认配置');\n\n // 如果没有任何策略,创建一个默认策略包含所有页面\n const allPageNames = entryFiles.map(f => f.name);\n const defaultConfig = generateStrategyConfig(\n 'default',\n allPageNames,\n entryFiles,\n {},\n pageConfigs,\n template,\n placeholder,\n log\n );\n\n buildConfigs['default'] = defaultConfig;\n }\n\n const strategyNames = Object.keys(buildConfigs);\n log(`📦 构建策略: ${strategyNames.join(', ')}`);\n return buildConfigs;\n } catch (error) {\n log('生成构建配置失败:', error);\n throw error;\n }\n}\n\n/**\n * 为特定策略生成构建配置\n */\nfunction generateStrategyConfig(\n strategyName: string,\n pages: string[],\n entryFiles: Array<{ name: string; file: string }>,\n strategyConfig: ConfigStrategy | undefined,\n pageConfigs: any,\n defaultTemplate: string,\n placeholder: string,\n log: (...args: any[]) => void\n): UserConfig {\n const htmlInputs: Record<string, string> = {};\n const tempFiles: string[] = [];\n\n // 收集所有页面的 define 变量\n const allPageDefines: Record<string, any> = {};\n\n // 为每个页面确定使用的HTML模板并创建临时文件\n for (const pageName of pages) {\n const entryFile = entryFiles.find(f => f.name === pageName);\n if (!entryFile) continue;\n\n // 获取页面配置\n const pageContext = {\n pageName,\n filePath: entryFile.file,\n relativePath: path.relative(process.cwd(), entryFile.file),\n strategy: strategyName,\n } as PageConfigContext;\n\n const pageConfig = getPageConfig(pageConfigs, pageContext, log);\n\n // 收集页面级 define 变量\n if (pageConfig?.define) {\n Object.assign(allPageDefines, pageConfig.define);\n }\n\n // 确定HTML模板\n let templatePath = defaultTemplate;\n\n // 1. 页面特定模板(如 mobile.html 对应 mobile 页面)\n const pageSpecificTemplate = `${pageName}.html`;\n if (fs.existsSync(path.resolve(process.cwd(), pageSpecificTemplate))) {\n templatePath = pageSpecificTemplate;\n }\n // 2. 页面配置中指定的模板\n else if (pageConfig?.template) {\n templatePath = pageConfig.template;\n }\n\n // 读取模板内容\n const templateFullPath = path.resolve(process.cwd(), templatePath);\n if (!fs.existsSync(templateFullPath)) {\n log(`警告: 模板文件不存在: ${templatePath}`);\n continue;\n }\n\n let templateContent = fs.readFileSync(templateFullPath, 'utf-8');\n\n // 替换占位符\n if (templateContent.includes(placeholder)) {\n // 临时HTML在项目根目录中,使用相对路径\n const entryPath = `./${entryFile.file}`;\n templateContent = templateContent.replace(\n new RegExp(placeholder.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&'), 'g'),\n entryPath\n );\n }\n\n // 创建临时HTML文件,使用新的命名规则:.temp.mp.[name].html\n const tempHtmlPath = path.resolve(process.cwd(), `.temp.mp.${pageName}.html`);\n fs.writeFileSync(tempHtmlPath, templateContent);\n tempFiles.push(tempHtmlPath);\n\n htmlInputs[pageName] = tempHtmlPath;\n }\n\n // 构建基础配置 - 直接输出到策略目录\n const baseConfig: UserConfig = {\n base: './', // 使用相对路径,支持子目录部署\n build: {\n outDir: `dist/${strategyName}`, // 直接输出到策略目录\n rollupOptions: {\n input: htmlInputs, // 使用临时HTML文件作为输入\n output: {\n entryFileNames: 'assets/[name]-[hash].js',\n chunkFileNames: 'assets/[name]-[hash].js',\n assetFileNames: 'assets/[name]-[hash][extname]',\n },\n },\n emptyOutDir: false, // 不清空输出目录,避免删除临时HTML文件\n },\n define: {},\n };\n\n // 使用Vite的mergeConfig进行智能深度合并\n let config: UserConfig = baseConfig;\n\n if (strategyConfig) {\n config = mergeConfig(baseConfig, strategyConfig);\n }\n\n // 合并页面级 define 变量到 Vite 的 define 配置中\n // 页面级 define 优先级高于策略级 define\n if (Object.keys(allPageDefines).length > 0) {\n config.define = {\n ...config.define,\n ...allPageDefines,\n };\n }\n\n // 手动处理需要特殊控制的配置项,防止被mergeConfig覆盖\n if (!config.build) config.build = {};\n if (!config.build.rollupOptions) config.build.rollupOptions = {};\n\n // 确保关键配置不被覆盖\n config.build.rollupOptions.input = htmlInputs; // 强制使用临时HTML文件作为输入\n config.build.emptyOutDir = false; // 不清空输出目录,避免删除临时HTML文件\n\n // 简化日志输出\n log(`策略 \"${strategyName}\" - ${pages.length} 个页面`);\n\n return config;\n}\n\n/**\n * 获取Vite配置的输出目录\n * 需要传入已解析的Vite配置或命令行参数\n */\nexport function getViteOutputDirectory(viteBuildArgs: string[] = []): string {\n // 1. 首先检查命令行参数中的 --outDir\n const outDirIndex = viteBuildArgs.findIndex(arg => arg === '--outDir');\n if (outDirIndex !== -1 && outDirIndex + 1 < viteBuildArgs.length) {\n const outDir = viteBuildArgs[outDirIndex + 1];\n return path.resolve(process.cwd(), outDir);\n }\n\n // 2. 检查 --outDir=value 格式\n const outDirArg = viteBuildArgs.find(arg => arg.startsWith('--outDir='));\n if (outDirArg) {\n const outDir = outDirArg.split('=')[1];\n return path.resolve(process.cwd(), outDir);\n }\n\n // 3. 如果没有命令行参数,使用 Vite 默认值\n // 注意:如果用户在 vite.config.ts 中配置了 build.outDir,\n // Vite 会自动使用该配置,我们这里只处理命令行参数的情况\n return path.resolve(process.cwd(), 'dist');\n}\n\n/**\n * 清理Vite配置的输出目录\n */\nexport function cleanViteOutputDirectory(viteBuildArgs: string[] = []): void {\n const outputDir = getViteOutputDirectory(viteBuildArgs);\n const log = createLogger(true);\n\n try {\n if (fs.existsSync(outputDir)) {\n fs.rmSync(outputDir, { recursive: true, force: true });\n log(`🧹 清理输出目录: ${path.relative(process.cwd(), outputDir)}`);\n }\n } catch (error) {\n log(`⚠️ 清理输出目录失败: ${outputDir}`, error);\n }\n}\n\n/**\n * 获取所有可用的构建策略\n */\nexport function discoverPages(options: BuildConfigOptions): Array<{ name: string; file: string }> {\n const { entry = 'src/pages/*/main.{ts,js}', exclude = [] } = options;\n\n const log = createLogger(true);\n\n try {\n // 发现所有页面入口文件\n const allFiles = glob.sync(entry, { cwd: process.cwd() });\n const entryFiles = filterEntryFiles(allFiles, entry, exclude, log);\n\n return entryFiles;\n } catch (error) {\n log('发现页面失败:', error);\n throw error;\n }\n}\n\nexport function getAvailableStrategies(options: BuildConfigOptions): string[] {\n const { entry = 'src/pages/*/main.{ts,js}', exclude = [], pageConfigs = {} } = options;\n\n const log = createLogger(false); // 静默模式\n const strategySet = new Set<string>();\n\n // 发现所有页面入口文件\n const allFiles = glob.sync(entry, { cwd: process.cwd() });\n const entryFiles = filterEntryFiles(allFiles, entry, exclude, log);\n\n if (entryFiles.length === 0) {\n throw new Error(`未找到匹配的入口文件: ${entry}`);\n }\n\n try {\n // 分析每个页面的策略\n for (const entryFile of entryFiles) {\n const pageContext = {\n pageName: entryFile.name,\n filePath: entryFile.file,\n relativePath: path.relative(process.cwd(), entryFile.file),\n } as PageConfigContext;\n\n const pageConfig = getPageConfig(pageConfigs, pageContext, log);\n const strategyName = pageConfig?.strategy || 'default';\n strategySet.add(strategyName);\n }\n\n // 只返回实际有页面的策略,不添加空策略\n return Array.from(strategySet).sort();\n } catch (error) {\n log('获取可用策略失败:', error);\n return ['default'];\n }\n}\n","import * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport { Module } from 'node:module';\nimport type { Options } from './types';\n\n/**\n * 配置上下文\n */\nexport interface ConfigContext {\n mode: 'development' | 'production';\n command: 'serve' | 'build';\n isCLI?: boolean;\n}\n\n/**\n * 配置函数类型\n */\nexport type ConfigFunction = (context: ConfigContext) => Options;\n\n/**\n * 配置文件名列表(优先级从高到低)\n */\nconst CONFIG_FILES = [\n 'multipage.config.js',\n 'multipage.config.mjs',\n 'multipage.config.ts',\n] as const;\n\n/**\n * 检查是否存在自定义配置文件\n */\nexport function hasCustomConfig(): boolean {\n for (const filename of CONFIG_FILES) {\n const configPath = path.resolve(process.cwd(), filename);\n if (fs.existsSync(configPath)) {\n return true;\n }\n }\n return false;\n}\n\n/**\n * 加载用户的多页面配置\n */\nexport async function loadUserConfig(context: ConfigContext): Promise<Options | null> {\n // 尝试加载项目自定义配置\n const customConfig = await loadCustomConfig();\n\n if (customConfig) {\n // 使用项目自定义配置\n const result = customConfig(context);\n\n // 如果配置函数返回 undefined 或 null,视为空配置\n if (!result) {\n return {};\n }\n\n return result;\n }\n\n // 没有找到用户配置\n return null;\n}\n\n/**\n * 加载配置文件\n */\nasync function loadConfigFile(filePath: string): Promise<any> {\n // 处理 TypeScript 文件\n if (filePath.endsWith('.ts')) {\n try {\n const code = await fs.promises.readFile(filePath, 'utf-8');\n\n // 尝试动态导入 esbuild\n let esbuild: any;\n try {\n esbuild = await import('esbuild');\n } catch (importError) {\n // esbuild 不可用,给出友好的错误提示\n console.error('\\n❌ 无法加载 TypeScript 配置文件,因为找不到 esbuild 依赖。');\n console.error('\\n💡 请选择以下解决方案之一:');\n console.error(\n ' 1. 安装 esbuild (peerDependency):npm install esbuild@\">=0.19.3\" --save-dev'\n );\n console.error(' 2. 或者如果使用 Vite 项目,esbuild 通常已安装,请检查版本是否 >=0.19.3');\n console.error(\n ' 3. 使用 JavaScript 配置文件:将 multipage.config.ts 重命名为 multipage.config.js'\n );\n console.error(\n ' 4. 使用 ESM 配置文件:将 multipage.config.ts 重命名为 multipage.config.mjs\\n'\n );\n throw new Error(`需要 esbuild 依赖来处理 TypeScript 配置文件: ${path.basename(filePath)}`);\n }\n\n // 使用 esbuild 实时转译 TS → JS\n const result = await esbuild.transform(code, {\n loader: 'ts',\n format: 'cjs', // 使用 CommonJS 格式便于使用 Module._compile\n target: 'node16',\n sourcemap: false,\n });\n\n // 创建临时模块并编译\n const tempModule = new Module(filePath);\n tempModule.filename = filePath;\n tempModule.paths = (Module as any)._nodeModulePaths(path.dirname(filePath));\n\n // 编译代码\n (tempModule as any)._compile(result.code, filePath);\n\n return tempModule.exports;\n } catch (error) {\n // 如果是 esbuild 缺失的错误,直接抛出\n if (error instanceof Error && error.message.includes('需要 esbuild 依赖')) {\n throw error;\n }\n\n console.warn('esbuild 转译失败,尝试简单转换:', error);\n\n // 备选方案:简单的文本替换\n const code = await fs.promises.readFile(filePath, 'utf-8');\n const jsCode = code\n .replace(/export\\s+default\\s+/, 'module.exports = ')\n .replace(/import\\s+.*?from\\s+['\"][^'\"]*['\"];?\\s*/g, '')\n .replace(/:\\s*[^=,})\\]]+/g, ''); // 简单的类型注解移除\n\n const tempModule = new Module(filePath);\n tempModule.filename = filePath;\n tempModule.paths = (Module as any)._nodeModulePaths(path.dirname(filePath));\n\n (tempModule as any)._compile(jsCode, filePath);\n return tempModule.exports;\n }\n }\n\n // 处理 JavaScript 文件\n if (filePath.endsWith('.js') || filePath.endsWith('.mjs')) {\n const fileUrl = pathToFileURL(filePath).href;\n return import(`${fileUrl}?t=${Date.now()}`);\n }\n\n throw new Error(`不支持的配置文件类型: ${filePath}`);\n}\n\n/**\n * 加载项目自定义配置文件\n */\nasync function loadCustomConfig(): Promise<ConfigFunction | null> {\n const cwd = process.cwd();\n\n for (const configFile of CONFIG_FILES) {\n const configPath = path.resolve(cwd, configFile);\n\n if (fs.existsSync(configPath)) {\n try {\n const configModule = await loadConfigFile(configPath);\n const configFunction = configModule.default || configModule;\n\n if (typeof configFunction === 'function') {\n return configFunction;\n } else {\n console.warn(`配置文件 ${configFile} 必须默认导出一个函数`);\n }\n } catch (error) {\n if (configFile.endsWith('.ts')) {\n console.error(`加载TypeScript配置文件 ${configFile} 失败:`, error);\n console.log('提示:确保你的项目支持TypeScript,或者使用 .js/.mjs 配置文件');\n } else {\n console.error(`加载配置文件 ${configFile} 失败:`, error);\n }\n }\n }\n }\n\n return null;\n}\n","import type { MultiPageOptions } from './types';\n\n/**\n * 默认配置选项\n */\nexport const DEFAULT_CONFIG: Required<\n Omit<MultiPageOptions, '__forceBuildStrategy' | 'buildStrategies'>\n> = {\n entry: 'src/pages/**/*.{ts,js}',\n exclude: [],\n template: 'index.html',\n placeholder: '{{ENTRY_FILE}}',\n debug: false,\n strategies: {\n default: {},\n },\n pageConfigs: {},\n};\n\n/**\n * 合并用户配置和默认配置\n */\nexport function mergeWithDefaults(\n userConfig: MultiPageOptions | null | undefined\n): MultiPageOptions {\n if (!userConfig) {\n return { ...DEFAULT_CONFIG };\n }\n\n // 处理 buildStrategies 别名\n const strategies =\n userConfig.strategies ?? userConfig.buildStrategies ?? DEFAULT_CONFIG.strategies;\n\n return {\n entry: userConfig.entry ?? DEFAULT_CONFIG.entry,\n exclude: userConfig.exclude ?? DEFAULT_CONFIG.exclude,\n template: userConfig.template ?? DEFAULT_CONFIG.template,\n placeholder: userConfig.placeholder ?? DEFAULT_CONFIG.placeholder,\n debug: userConfig.debug ?? DEFAULT_CONFIG.debug,\n strategies,\n pageConfigs: userConfig.pageConfigs ?? DEFAULT_CONFIG.pageConfigs,\n __forceBuildStrategy: userConfig.__forceBuildStrategy,\n };\n}\n\n/**\n * 检查配置是否为空或无效\n */\nexport function isEmptyConfig(config: MultiPageOptions): boolean {\n // 检查是否是完全空的对象\n if (Object.keys(config).length === 0) {\n return true;\n }\n\n // 检查是否只有默认值或无效值\n const hasValidEntry = config.entry && config.entry !== DEFAULT_CONFIG.entry;\n const hasValidStrategies =\n (config.strategies && Object.keys(config.strategies).length > 0) ||\n (config.buildStrategies && Object.keys(config.buildStrategies).length > 0);\n const hasValidPageConfigs =\n config.pageConfigs &&\n (typeof config.pageConfigs === 'function' || Object.keys(config.pageConfigs).length > 0);\n\n return !hasValidEntry && !hasValidStrategies && !hasValidPageConfigs;\n}\n","import type { UserConfig } from 'vite';\n\n// 核心配置选项\nexport interface MultiPageOptions {\n entry?: string;\n exclude?: string[];\n template?: string;\n placeholder?: string;\n debug?: boolean;\n strategies?: Record<string, ConfigStrategy>;\n buildStrategies?: Record<string, ConfigStrategy>; // 别名,等同于 strategies\n pageConfigs?: Record<string, PageConfig> | PageConfigFunction;\n __forceBuildStrategy?: string;\n}\n\n// 主要导出类型\nexport type Options = MultiPageOptions;\n\n// 开发服务器选项\nexport interface DevServerOptions {\n entry: string;\n exclude: string[];\n template: string;\n placeholder: string;\n strategies?: Record<string, ConfigStrategy>;\n pageConfigs?: Record<string, PageConfig> | PageConfigFunction;\n appliedStrategies?: Map<string, string>;\n devStrategy?: string; // 开发模式下指定的策略\n}\n\n// 构建配置选项\nexport interface BuildConfigOptions {\n entry: string;\n exclude: string[];\n template: string;\n placeholder: string;\n strategies?: Record<string, ConfigStrategy>;\n pageConfigs?: Record<string, PageConfig> | PageConfigFunction;\n forceBuildStrategy?: string;\n}\n\n// 策略配置\nexport interface ConfigStrategy extends Omit<UserConfig, 'plugins'> {}\n\n// 页面配置\nexport interface PageConfig {\n strategy?: string;\n define?: Record<string, any>;\n template?: string;\n viteConfig?: UserConfig;\n match?: string;\n}\n\n// 页面上下文\nexport interface PageContext {\n pageName: string;\n filePath: string;\n relativePath: string;\n fullPath?: string;\n strategy?: string;\n isMatched?: boolean;\n}\n\n// 页面配置上下文(别名)\nexport type PageConfigContext = PageContext;\n\n// 页面配置函数\nexport type PageConfigFunction = (context: PageContext) => PageConfig | null;\n\n// 入口文件信息\nexport interface EntryFile {\n name: string;\n file: string;\n}\n\n// 候选文件信息\nexport interface CandidateFile extends EntryFile {\n priority: number;\n}\n\n// 构建策略配置\nexport interface BuildStrategyConfig {\n strategy: string;\n pages: string[];\n configPath?: string;\n}\n\n// CLI选项\nexport interface CLIOptions {\n configFile: string;\n outDir?: string;\n debug?: boolean;\n mode?: string;\n minify?: boolean | string;\n build?: Record<string, any>;\n base?: string;\n strategy?: string;\n port?: number | string;\n host?: string;\n https?: boolean;\n open?: boolean;\n}\n\n// 插件上下文\nexport interface PluginContext {\n mode: string;\n command: 'build' | 'serve';\n isCLI: boolean;\n}\n\n// 配置函数类型\nexport type ConfigFunction = (context: PluginContext) => MultiPageOptions;\n\n// 配置变换函数类型\nexport type ConfigTransformFunction = (\n config: MultiPageOptions,\n context: PluginContext\n) => MultiPageOptions;\n\n// 工具函数:定义配置\nexport function defineConfig(config: MultiPageOptions | ConfigFunction): ConfigFunction {\n // 如果传入的是函数,直接返回\n if (typeof config === 'function') {\n return config;\n }\n\n // 如果传入的是对象,包装成函数返回\n return () => config;\n}\n\n// 工具函数:定义配置变换\nexport function defineConfigTransform(transform: ConfigTransformFunction): ConfigTransformFunction {\n return transform;\n}\n"],"mappings":"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