@open-wxa/wxa-runner-core 0.2.1 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/server/DevServer.ts","../src/project/AppBuilder.ts","../src/project/wxConfig.ts","../src/project/MiniProgram.ts","../src/server/html.ts","../src/server/ignoreList.ts","../src/server/proxy.ts","../src/server/hmr.ts","../src/server/realLogin.ts","../src/server/setup.ts","../src/server/watcher.ts"],"sourcesContent":["/**\n * 开发服务器\n *\n * / 宿主页面\n * /__wxa__/app-info.json 构建信息(__wxConfig、页面列表、设备参数、setup)\n * /__wxa__/setup.js 编译后的 setup 文件\n * /__wxa__/appservice.html 逻辑层\n * /__wxa__/webview/<page>.html 渲染层(路径与页面一致,wxml 里的相对资源路径因此能正确解析)\n * /__wxa__/module/<file>.js 单个业务 JS 模块\n * /__wxa__/service-templates.js、page-templates.js 两层的模板(热更新时重新下发)\n * /__wxa__/page-styles.js 渲染层的样式(热更新时重新下发)\n * /__baselib__/* 官方基础库\n * /__wxa__/proxy?url= 网络代理\n * /__wxa__/tmp 临时文件:POST ?type=png 保存,返回可直接访问的地址\n * /__wxa__/region/<kind> 地区选择器用的行政区划数据(districts / streets),取自本机开发者工具\n * /__wxa__/api/* 真实登录(--real-login):wx.login 的 code、手机号,只允许本机访问\n * /__wxa__/ws 文件变化通知:热更新(hot)或整页刷新(reload)\n * 其余路径 小程序目录下的静态资源\n */\n\nimport { randomUUID } from 'crypto';\nimport fs from 'fs';\nimport http from 'http';\nimport path from 'path';\nimport express, { Express, NextFunction, Request, Response } from 'express';\nimport { WebSocket, WebSocketServer } from 'ws';\nimport { findDeveloperTicket, findRegionDataFile, resolveBaseLib, ResolvedBaseLib, usesIsolatedContext } from '@open-wxa/wxa-runner-extractor';\nimport { AppBuilder, BuildResult } from '../project/AppBuilder';\nimport { MiniProgram } from '../project/MiniProgram';\nimport type { AppInfo, DeviceProfile } from '../runtime/types';\nimport { appserviceHtml, hostHtml, subcontextHtml, webviewHtml } from './html';\nimport { ignoreListSourceMap } from './ignoreList';\nimport { handleProxy } from './proxy';\nimport { planHotUpdate } from './hmr';\nimport { realLoginRouter, requireUsableTicket } from './realLogin';\nimport { bundleSetup, collectSetupEnv, findSetupFile } from './setup';\nimport { ProjectWatcher, watchProject } from './watcher';\n\nexport interface DevServerOptions {\n projectPath: string;\n /** 默认 3000,传 0 使用随机端口 */\n port?: number;\n host?: string;\n /** 指定基础库版本,默认使用本机已安装的最新兼容版本 */\n baseLibVersion?: string;\n baseLibCacheDir?: string;\n watch?: boolean;\n device?: Partial<DeviceProfile>;\n /**\n * 小程序启动前执行的 setup 文件;默认使用项目目录下的 wxa.setup.ts / .js / .mjs,传 false 不使用\n * 环境变量在构造时读取,只暴露 WXA_ 前缀的变量\n */\n setup?: string | false;\n /** 浏览器端运行时(host.js / frame.js)所在目录,默认取构建产物 */\n runtimeDir?: string;\n /** 使用本机微信开发者工具登录态获取真实 code 和手机号 */\n realLogin?: boolean;\n logger?: Pick<Console, 'log' | 'warn' | 'error'>;\n}\n\nexport const DEFAULT_DEVICE: DeviceProfile = {\n model: 'iPhone X',\n screenWidth: 375,\n screenHeight: 812,\n pixelRatio: 3,\n statusBarHeight: 44,\n safeAreaBottom: 34,\n system: 'iOS 16.0',\n platform: 'devtools'\n};\n\nexport class DevServer {\n readonly project: MiniProgram;\n readonly builder: AppBuilder;\n readonly baseLib: ResolvedBaseLib;\n /** 生效的 setup 文件绝对路径,没有时为 null */\n readonly setupFile: string | null;\n private readonly setupEnv: Record<string, string>;\n private readonly device: DeviceProfile;\n private readonly logger: Pick<Console, 'log' | 'warn' | 'error'>;\n private readonly app: Express;\n private server: http.Server | null = null;\n private sockets: WebSocketServer | null = null;\n private watcher: ProjectWatcher | null = null;\n private address = '';\n /** 运行时生成的临时文件(canvas 导出、拍照等),随服务器进程存在 */\n private readonly tempFiles = new Map<string, Buffer>();\n\n constructor(private readonly options: DevServerOptions) {\n this.logger = options.logger || console;\n this.project = new MiniProgram(path.resolve(options.projectPath));\n this.baseLib = resolveBaseLib({ version: options.baseLibVersion, cacheDir: options.baseLibCacheDir });\n if (!this.baseLib.compatible) {\n this.logger.warn(`[wxa-runner] 基础库 ${this.baseLib.version} 高于已验证的版本范围,可能无法正常运行`);\n }\n if (options.realLogin) {\n try {\n requireUsableTicket(findDeveloperTicket());\n } catch (error) {\n this.logger.warn(`[wxa-runner] 真实登录暂不可用:${(error as Error).message}`);\n }\n }\n this.device = { ...DEFAULT_DEVICE, ...options.device };\n this.builder = new AppBuilder(this.project);\n this.setupFile = resolveSetupFile(options.setup, this.project.projectPath);\n this.setupEnv = collectSetupEnv(process.env);\n this.app = this.createApp();\n }\n\n get url(): string {\n return this.address;\n }\n\n async start(): Promise<string> {\n // 先构建一次:编译错误在终端里立即可见\n try {\n const build = await this.builder.build();\n this.logger.log(`[wxa-runner] 编译完成:${build.pages.length} 个页面,${build.components.length} 个组件,${build.durationMs}ms`);\n } catch (error) {\n this.logger.error(`[wxa-runner] 编译失败:\\n${(error as Error).message}`);\n }\n\n const server = http.createServer(this.app);\n this.sockets = new WebSocketServer({ noServer: true });\n server.on('upgrade', (req, socket, head) => {\n if (req.url?.startsWith('/__wxa__/ws')) {\n this.sockets!.handleUpgrade(req, socket, head, ws => this.sockets!.emit('connection', ws, req));\n } else {\n socket.destroy();\n }\n });\n\n const host = this.options.host || 'localhost';\n await new Promise<void>((resolve, reject) => {\n server.once('error', reject);\n server.listen(this.options.port ?? 3000, host, () => resolve());\n });\n this.server = server;\n\n const { port } = server.address() as { port: number };\n this.address = `http://${host}:${port}`;\n\n if (this.options.watch !== false) this.startWatching();\n return this.address;\n }\n\n async close() {\n await this.watcher?.close();\n this.sockets?.clients.forEach(client => client.terminate());\n this.sockets?.close();\n const server = this.server;\n this.server = null;\n if (server) {\n // 浏览器和 fetch 的 keep-alive 连接会让 close() 一直等到超时\n server.closeAllConnections();\n await new Promise<void>(resolve => server.close(() => resolve()));\n }\n }\n\n private createApp(): Express {\n const app = express();\n const root = this.project.root;\n const noStore = (res: Response) => res.setHeader('Cache-Control', 'no-store');\n\n app.get('/', (_req, res) => {\n noStore(res);\n res.type('html').send(hostHtml());\n });\n\n app.get('/favicon.ico', (_req, res) => {\n res.status(204).end();\n });\n\n app.get('/__wxa__/app-info.json', async (_req, res) => {\n noStore(res);\n const base = { appid: this.project.appid, baseLibVersion: this.baseLib.version, device: this.device, realLogin: this.options.realLogin === true };\n try {\n const build = await this.builder.build();\n const info: AppInfo = { ...base, version: build.version, wxConfig: build.wxConfig, pages: build.pages };\n if (this.setupFile) info.setup = await this.setupInfo(this.setupFile);\n res.json(info);\n } catch (error) {\n res.json({ ...base, version: 0, wxConfig: {}, pages: [], buildError: (error as Error).message });\n }\n });\n\n app.get('/__wxa__/setup.js', async (_req, res) => {\n noStore(res);\n if (!this.setupFile) {\n res.status(404).type('text').send('没有 setup 文件');\n return;\n }\n try {\n res.type('js').send(await bundleSetup(this.setupFile));\n } catch (error) {\n res.status(500).type('text').send((error as Error).message);\n }\n });\n\n app.get('/__wxa__/appservice.html', this.withBuild((build, req, res) => {\n const launchPath = String(req.query.path || '').replace(/^\\//, '');\n const launch = build.pages.includes(launchPath)\n ? { path: launchPath, query: Object.fromEntries(new URLSearchParams(String(req.query.query || ''))) }\n : { path: build.pages[0], query: {} };\n res.type('html').send(appserviceHtml(build, launch, { hot: this.hotEnabled, isolated: usesIsolatedContext(this.baseLib.version) }));\n }));\n\n app.get('/__wxa__/subcontext.html', (req, res) => {\n const id = Number(req.query.id);\n if (!Number.isInteger(id) || id <= 0) {\n res.status(400).type('text').send('子上下文 id 无效');\n return;\n }\n res.type('html').send(subcontextHtml(id, req.query.empty === '1'));\n });\n\n app.get(/^\\/__wxa__\\/webview\\/(.+)\\.html$/, this.withBuild((build, req, res, next) => {\n const pagePath = decodeURIComponent(req.params[0]);\n if (!build.pages.includes(pagePath)) return next();\n res.type('html').send(webviewHtml(build, pagePath, Number(req.query.id), { hot: this.hotEnabled }));\n }));\n\n app.get(/^\\/__wxa__\\/module\\/(.+\\.js)$/, this.withBuild((build, req, res) => {\n const id = decodeURIComponent(req.params[0]);\n if (!build.modules.includes(id)) {\n res.status(404).type('js').send(`console.error(${JSON.stringify(`[wxa-runner] 模块 ${id} 不存在`)})`);\n return;\n }\n res.type('js').send(this.builder.getModuleScript(id));\n }));\n\n app.get('/__wxa__/service-bootstrap.js', this.withBuild((build, _req, res) => {\n res.type('js').send(build.serviceBootstrap);\n }));\n\n app.get('/__wxa__/service-templates.js', this.withBuild((build, _req, res) => {\n res.type('js').send(build.serviceTemplates);\n }));\n\n app.get('/__wxa__/page-templates.js', this.withBuild((build, _req, res) => {\n res.type('js').send(build.pageTemplates);\n }));\n\n app.get('/__wxa__/page-styles.js', this.withBuild((build, _req, res) => {\n res.type('js').send(build.pageStyles);\n }));\n\n app.get('/__wxa__/page-frame.js', this.withBuild((build, _req, res) => {\n res.type('js').send(build.pageFrame);\n }));\n\n app.use('/__wxa__/runtime', express.static(this.runtimeDir(), { cacheControl: false }));\n // 基础库脚本加入 DevTools 的忽略列表,原始代码就是脚本本身(换个地址,免得和脚本混为一个文件)\n const sourceMaps = new Map<string, string>();\n app.get(/^\\/__baselib__\\/(.+\\.js)\\.map$/, (req, res, next) => {\n const file = path.resolve(this.baseLib.dir, req.params[0]);\n if (!file.startsWith(this.baseLib.dir + path.sep) || !fs.existsSync(file)) return next();\n if (!sourceMaps.has(file)) sourceMaps.set(file, ignoreListSourceMap(fs.readFileSync(file, 'utf8'), `${path.basename(file)}?original`));\n res.type('json').send(sourceMaps.get(file));\n });\n app.use('/__baselib__', express.static(this.baseLib.dir, {\n maxAge: '1d',\n setHeaders: (res, file) => {\n if (file.endsWith('.js')) res.setHeader('SourceMap', `${path.basename(file)}.map`);\n }\n }));\n app.all('/__wxa__/proxy', express.raw({ type: () => true, limit: '100mb' }), handleProxy);\n\n // 返回完整的 http 地址:image、video 组件只原样使用 http(s) 地址,其余的会被当成页面相对路径\n app.post('/__wxa__/tmp', express.raw({ type: () => true, limit: '100mb' }), (req, res) => {\n const type = String(req.query.type || 'png');\n if (!/^[a-z0-9]+$/i.test(type)) {\n res.status(400).type('text').send(`非法的文件类型 ${type}`);\n return;\n }\n const name = `${randomUUID()}.${type}`;\n this.tempFiles.set(name, Buffer.isBuffer(req.body) ? req.body : Buffer.alloc(0));\n res.json({ url: `${req.protocol}://${req.get('host')}/__wxa__/tmp/${name}` });\n });\n app.get('/__wxa__/region/:kind', (req, res) => {\n const kind = req.params.kind;\n const file = kind === 'districts' || kind === 'streets' ? findRegionDataFile(kind) : null;\n if (!file) {\n res.status(404).end();\n return;\n }\n res.type('text/plain; charset=utf-8').sendFile(file, { maxAge: '1d' });\n });\n\n app.get('/__wxa__/tmp/:name', (req, res) => {\n const data = this.tempFiles.get(req.params.name);\n if (!data) {\n res.status(404).end();\n return;\n }\n res.type(path.extname(req.params.name)).send(data);\n });\n\n if (this.options.realLogin) app.use('/__wxa__/api', realLoginRouter(this.project.appid));\n\n // 渲染层页面路径下的相对资源(如 ../../images/a.png)\n app.use('/__wxa__/webview', express.static(root));\n app.use(express.static(root));\n\n app.use((error: Error, _req: Request, res: Response, _next: NextFunction) => {\n this.logger.error('[wxa-runner]', error);\n res.status(500).type('text').send(error.message);\n });\n\n return app;\n }\n\n /** 先编译一遍 setup:编译错误随 app-info 交给宿主显示,而不是让浏览器 import 失败 */\n private async setupInfo(file: string): Promise<NonNullable<AppInfo['setup']>> {\n try {\n await bundleSetup(file);\n return { env: this.setupEnv };\n } catch (error) {\n return { env: this.setupEnv, error: (error as Error).message };\n }\n }\n\n /** 需要构建结果的路由;构建失败时返回错误文本 */\n private withBuild(handler: (build: BuildResult, req: Request, res: Response, next: NextFunction) => void) {\n return async (req: Request, res: Response, next: NextFunction) => {\n res.setHeader('Cache-Control', 'no-store');\n let build: BuildResult;\n try {\n build = await this.builder.build();\n } catch (error) {\n res.status(500).type('text').send((error as Error).message);\n return;\n }\n try {\n handler(build, req, res, next);\n } catch (error) {\n next(error);\n }\n };\n }\n\n private runtimeDir(): string {\n const candidates = [\n this.options.runtimeDir,\n path.join(__dirname, 'runtime'),\n path.join(__dirname, '../runtime'),\n path.join(__dirname, '../../dist/runtime')\n ].filter((dir): dir is string => !!dir);\n const found = candidates.find(dir => fs.existsSync(path.join(dir, 'host.js')));\n if (!found) {\n throw new Error('找不到浏览器端运行时(host.js),请先执行 pnpm build');\n }\n return found;\n }\n\n /** 只在监听文件变化时打开基础库的热更新开关,跑测试(watch: false)时运行环境与真机一致 */\n private get hotEnabled(): boolean {\n return this.options.watch !== false;\n }\n\n private startWatching() {\n // 上次编译失败时页面上显示着错误,修好后整页刷新才能清掉\n let lastBuildFailed = false;\n\n // 项目目录下的 setup 文件不一定在小程序根目录(miniprogramRoot)里\n const watched = [this.project.root];\n if (this.setupFile && path.relative(this.project.root, this.setupFile).startsWith('..')) watched.push(this.setupFile);\n\n this.watcher = watchProject(this.project.root, watched, async ({ files, structural }) => {\n const reloadOnly = structural || lastBuildFailed;\n this.builder.invalidate(files);\n const label = files.length > 3 ? `${files.slice(0, 3).join(', ')} 等 ${files.length} 个文件` : files.join(', ');\n\n try {\n const build = await this.builder.build();\n lastBuildFailed = false;\n const plan = reloadOnly ? null : planHotUpdate(files, build);\n this.logger.log(`[wxa-runner] ${label} 已更新,重新编译 ${build.durationMs}ms${plan ? ',热更新' : ''}`);\n this.broadcast(plan ? { type: 'hot', version: build.version, ...plan } : { type: 'reload' });\n } catch (error) {\n lastBuildFailed = true;\n const message = (error as Error).message;\n this.logger.error(`[wxa-runner] 编译失败:\\n${message}`);\n this.broadcast({ type: 'error', message });\n }\n });\n }\n\n private broadcast(message: Record<string, unknown>) {\n const data = JSON.stringify(message);\n this.sockets?.clients.forEach(client => {\n if (client.readyState === WebSocket.OPEN) client.send(data);\n });\n }\n}\n\nfunction resolveSetupFile(option: string | false | undefined, projectPath: string): string | null {\n if (option === false) return null;\n if (!option) return findSetupFile(projectPath);\n const file = path.resolve(option);\n if (!fs.existsSync(file)) throw new Error(`找不到 setup 文件:${file}`);\n return file;\n}\n","/**\n * 把小程序项目构建成官方基础库能直接加载的形式\n *\n * 逻辑层(appservice):\n * 1. 裁剪版 $gwx(wcc -xc),基础库用它了解组件的节点结构\n * 2. 每个 JS 文件单独一个 define(\"x.js\", fn) 脚本(便于在 DevTools 里按原文件调试)\n * 3. bootstrap:注册 __wxAppCode__ 的 json/wxml(serviceTemplates,热更新时单独下发),\n * require(\"app.js\"),再依次 require 组件和页面\n *\n * 渲染层(page-frame):\n * 1. 完整版 $gwx(wcc -cc)与每个页面/组件的 __wxAppCode__ json / wxml(pageTemplates)\n * 2. wcsc 的公共样式运行时 + app.wxss,与每个页面/组件的 __wxAppCode__ wxss(pageStyles)\n * 两部分在热更新时分别下发\n * 样式和模板里的 env(safe-area-inset-*) 改写成 CSS 变量,见 rewriteSafeAreaInsets\n */\n\nimport { compileWxml, compileWxss, ComponentTags } from '@open-wxa/wxa-runner-compiler';\nimport { transformSync } from 'esbuild';\nimport { AppJson, MiniProgram } from './MiniProgram';\nimport { createWxConfig } from './wxConfig';\n\nexport interface BuildResult {\n version: number;\n wxConfig: Record<string, any>;\n /** 所有 JS 模块 id(如 \"pages/index/index.js\"),逻辑层按此顺序加载 define 脚本 */\n modules: string[];\n /** 逻辑层的 $gwx 与 __wxAppCode__ json/wxml,不含 require */\n serviceTemplates: string;\n serviceBootstrap: string;\n pageFrame: string;\n /** 渲染层的 $gwx 与 __wxAppCode__ json/wxml,不含样式(热更新时单独下发) */\n pageTemplates: string;\n /** 渲染层的样式:公共样式立即注入,页面和组件的样式注册到 __wxAppCode__(热更新时单独下发) */\n pageStyles: string;\n pages: string[];\n components: string[];\n durationMs: number;\n}\n\n/** define 包装函数的参数:屏蔽浏览器全局对象,与开发者工具一致 */\nconst MODULE_PARAMS = [\n 'require', 'module', 'exports', 'window', 'document', 'frames', 'self', 'location', 'navigator',\n 'localStorage', 'history', 'Caches', 'screen', 'alert', 'confirm', 'prompt', 'XMLHttpRequest',\n 'WebSocket', 'Reporter', 'webkit', 'WeixinJSCore'\n].join(', ');\n\n/** 行首的 import / export 语句(不匹配 import() 动态导入) */\nconst ESM_SYNTAX = /^\\s*(import\\s*[\\w*{'\"]|export\\s+(default|const|let|var|function|class|async|\\{|\\*))/m;\n\ntype ChangeKind = 'config' | 'wxml' | 'wxss' | 'js';\n\nexport class AppBuilder {\n private version = 0;\n /** invalidate 标记过的变化次数;每次构建记下开始时覆盖到第几次 */\n private changes = 0;\n private pending: { covers: number; result: Promise<BuildResult> } | null = null;\n private last: BuildResult | null = null;\n private dirty = new Set<ChangeKind>(['config', 'wxml', 'wxss', 'js']);\n private wxmlCache: { full: string; cut: string } | null = null;\n private wxssCache: { comm: string; pages: Record<string, string> } | null = null;\n\n constructor(readonly project: MiniProgram) {}\n\n /** 根据变化的文件标记需要重新构建的部分;返回 true 表示需要刷新 */\n invalidate(changedFiles: string[] = []): boolean {\n if (changedFiles.length === 0) {\n ['config', 'wxml', 'wxss', 'js'].forEach(kind => this.dirty.add(kind as ChangeKind));\n this.changes++;\n return true;\n }\n\n let relevant = false;\n for (const file of changedFiles) {\n const kind = classifyFile(file);\n if (!kind) continue;\n relevant = true;\n this.dirty.add(kind);\n // json 决定组件关系,wxml/wxss 的编译参数都依赖它\n if (kind === 'config') {\n this.dirty.add('wxml');\n this.dirty.add('wxss');\n }\n }\n if (relevant) this.changes++;\n return relevant;\n }\n\n /**\n * 返回包含调用前所有变化的构建结果。并发调用共享同一次构建;\n * 进行中的构建开始得比最近一次变化早时(可能已读到旧文件),等它结束后再构建一次\n */\n async build(): Promise<BuildResult> {\n const wanted = this.changes;\n for (;;) {\n if (!this.pending && this.dirty.size === 0 && this.last) return this.last;\n if (!this.pending) {\n const result = this.doBuild().finally(() => {\n this.pending = null;\n });\n this.pending = { covers: this.changes, result };\n }\n const { covers, result } = this.pending;\n const built = await result;\n if (covers >= wanted) return built;\n }\n }\n\n /**\n * 单个 JS 模块的 define 包装\n *\n * 包装头和代码第一行写在同一行,行号与源文件一致。使用 import/export 的文件\n * 先转换为 CommonJS(对应开发者工具的「ES6 转 ES5」),并内联 source map。\n */\n getModuleScript(id: string): string {\n let code = this.project.readText(id);\n if (ESM_SYNTAX.test(code)) {\n try {\n code = transformSync(code, {\n format: 'cjs',\n loader: 'js',\n target: 'es2020',\n sourcefile: id,\n sourcemap: 'inline'\n }).code;\n } catch (error) {\n const message = `[wxa-runner] ${id} 转换失败:${(error as Error).message}`;\n return `console.error(${JSON.stringify(message)});\\n`;\n }\n }\n return `define(${JSON.stringify(id)}, function(${MODULE_PARAMS}){${code}\\n});\\n`;\n }\n\n private async doBuild(): Promise<BuildResult> {\n const start = Date.now();\n const dirty = new Set(this.dirty);\n this.dirty.clear();\n\n try {\n const app = this.project.readAppJson();\n const { pages, components } = this.project.collectUnits(app);\n const units = [...components, ...pages];\n const unitJson = new Map(units.map(id => [id, this.project.readUnitJson(id, app)]));\n\n const [wxml, wxss] = await Promise.all([\n dirty.has('wxml') || !this.wxmlCache ? this.compileTemplates(units, unitJson) : this.wxmlCache,\n dirty.has('wxss') || !this.wxssCache ? this.compileStyles(units) : this.wxssCache\n ]);\n this.wxmlCache = wxml;\n this.wxssCache = wxss;\n\n const workersDir = typeof app.workers === 'string' ? app.workers : app.workers?.path;\n const modules = this.project.listFiles(['.js'], workersDir ? [workersDir] : []);\n\n const serviceTemplates = this.createTemplates(app, units, unitJson, wxml.cut);\n const pageTemplates = this.createTemplates(app, units, unitJson, wxml.full);\n const pageStyles = createStyles(units, wxss);\n const result: BuildResult = {\n version: ++this.version,\n wxConfig: createWxConfig({\n app,\n pages,\n unitJson,\n appid: this.project.appid,\n subPackages: this.project.subPackages(app)\n }),\n modules,\n serviceTemplates,\n serviceBootstrap: this.createServiceBootstrap(serviceTemplates, units, modules),\n pageFrame: `${pageTemplates}\\n${pageStyles}`,\n pageTemplates,\n pageStyles,\n pages,\n components,\n durationMs: Date.now() - start\n };\n this.last = result;\n return result;\n } catch (error) {\n // 构建失败时保留脏标记,下次请求重新尝试\n dirty.forEach(kind => this.dirty.add(kind));\n throw error;\n }\n }\n\n private async compileTemplates(units: string[], unitJson: Map<string, any>) {\n const files = this.project.listFiles(['.wxml', '.wxs']).map(f => './' + f);\n const components: ComponentTags[] = units\n .filter(id => this.project.exists(id + '.wxml'))\n .map(id => {\n const json = unitJson.get(id);\n return { path: id, tags: Object.keys({ ...json.usingComponents, ...(json.componentGenerics || {}) }) };\n });\n\n const cwd = this.project.root;\n const [full, cut] = await Promise.all([\n compileWxml({ cwd, files, components, cut: false }),\n compileWxml({ cwd, files, components, cut: true })\n ]);\n // 只有渲染层的完整版带内联 style\n return { full: rewriteSafeAreaInsets(full), cut };\n }\n\n /** 与开发者工具一致,项目里的 wxss 全部交给 wcsc:被 @import 的文件(如 uni-app 的 common/main.wxss)也要在列表里 */\n private async compileStyles(units: string[]) {\n const pageFiles = units.filter(id => this.project.exists(id + '.wxss')).map(id => `./${id}.wxss`);\n const commonFiles = this.project.listFiles(['.wxss']).map(file => `./${file}`).filter(file => !pageFiles.includes(file));\n const { comm, pages } = await compileWxss({ cwd: this.project.root, pageFiles, commonFiles });\n return {\n comm: rewriteSafeAreaInsets(comm),\n pages: Object.fromEntries(Object.entries(pages).map(([file, css]) => [file, rewriteSafeAreaInsets(css)]))\n };\n }\n\n /** $gwx 加上各页面、组件的 json / wxml 注册;逻辑层用裁剪版 $gwx,渲染层用完整版 */\n private createTemplates(app: AppJson, units: string[], unitJson: Map<string, any>, gwx: string): string {\n const lines: string[] = [gwx, `__wxAppCode__[\"app.json\"] = ${toJs(app)};`];\n\n for (const id of units) {\n lines.push(`__wxAppCode__[${toJs(id + '.json')}] = ${toJs(unitJson.get(id))};`);\n if (this.project.exists(id + '.wxml')) {\n lines.push(`__wxAppCode__[${toJs(id + '.wxml')}] = $gwx(${toJs('./' + id + '.wxml')});`);\n }\n }\n return lines.join('\\n');\n }\n\n private createServiceBootstrap(serviceTemplates: string, units: string[], modules: string[]): string {\n const lines: string[] = [serviceTemplates];\n\n if (modules.includes('app.js')) {\n lines.push(requireStatement('app.js', null));\n }\n for (const id of units) {\n if (modules.includes(id + '.js')) lines.push(requireStatement(id + '.js', id));\n }\n return lines.join('\\n');\n }\n}\n\nfunction createStyles(units: string[], wxss: { comm: string; pages: Record<string, string> }): string {\n const lines = [wxss.comm];\n for (const id of units) {\n const css = wxss.pages[id + '.wxss'];\n if (css) lines.push(`__wxAppCode__[${toJs(id + '.wxss')}] = ${css};`);\n }\n return lines.join('\\n');\n}\n\n/** 某个模块执行出错时只影响它自己,其余页面照常注册 */\nfunction requireStatement(file: string, route: string | null): string {\n const routeSetup = route\n ? `__wxRoute = ${toJs(route)}; __wxRouteBegin = true; __wxAppCurrentFile__ = ${toJs(file)};`\n : `__wxAppCurrentFile__ = ${toJs(file)};`;\n return `${routeSetup}\\ntry { require(${toJs(file)}); } catch (error) { console.error(${toJs('[wxa-runner] ' + file + ' 执行出错\\n')}, error); }`;\n}\n\n/**\n * env(safe-area-inset-*) / constant(safe-area-inset-*) → var(--wxa-safe-area-inset-*)\n *\n * 桌面浏览器里这些 env() 恒为 0,而真机上的值取决于页面窗口是否贴着屏幕边缘(tabBar 页面底部为 0),\n * 所以改成变量,由宿主按页面写入(PageView.setSafeAreaInsets)。只替换函数名和参数名,\n * fallback 原样保留——wcsc 会把其中的 rpx 拆成数组片段,不能整体解析。\n */\nexport function rewriteSafeAreaInsets(code: string): string {\n return code.replace(/\\b(?:env|constant)\\(\\s*safe-area-inset-(top|right|bottom|left)\\b/g, 'var(--wxa-safe-area-inset-$1');\n}\n\n/** JSON 序列化并转义 </script> 与行分隔符,结果可安全嵌入脚本 */\nexport function toJs(value: unknown): string {\n return JSON.stringify(value)\n .replace(/</g, '\\\\u003c')\n .replace(/\\u2028/g, '\\\\u2028')\n .replace(/\\u2029/g, '\\\\u2029');\n}\n\nfunction classifyFile(file: string): ChangeKind | null {\n if (file.endsWith('.json')) return 'config';\n if (file.endsWith('.wxml') || file.endsWith('.wxs')) return 'wxml';\n if (file.endsWith('.wxss')) return 'wxss';\n if (file.endsWith('.js')) return 'js';\n return null;\n}\n","/**\n * 生成基础库读取的 __wxConfig,字段与开发者工具保持一致\n */\n\nimport { AppJson, UnitJson } from './MiniProgram';\n\nexport const DEFAULT_WINDOW = {\n navigationBarBackgroundColor: '#000000',\n navigationBarTextStyle: 'white',\n navigationBarTitleText: '',\n backgroundColor: '#ffffff',\n backgroundTextStyle: 'dark',\n enablePullDownRefresh: false,\n onReachBottomDistance: 50\n};\n\nexport interface WxConfigInput {\n app: AppJson;\n pages: string[];\n unitJson: Map<string, UnitJson>;\n appid: string;\n subPackages: NonNullable<AppJson['subPackages']>;\n}\n\nexport function createWxConfig(input: WxConfigInput): Record<string, any> {\n const { app, pages, unitJson, appid } = input;\n const entryPage = app.entryPagePath || app.pages[0];\n\n const page: Record<string, any> = {};\n for (const id of pages) {\n const { singlePage, ...window } = unitJson.get(id) || { usingComponents: {} };\n page[id + '.html'] = { window, singlePage, renderer: window.renderer };\n }\n\n const tabBar = app.tabBar\n ? { ...app.tabBar, list: app.tabBar.list.map(item => ({ ...item, pagePath: item.pagePath + '.html' })) }\n : undefined;\n\n return {\n ...app,\n pages: app.pages,\n resizable: !!app.resizable,\n subPackages: input.subPackages.length ? input.subPackages : undefined,\n debug: !!app.debug,\n widgets: app.widgets || [],\n customClose: !!app.customClose,\n workers: app.workers || '',\n cloud: !!app.cloud,\n global: { window: { ...DEFAULT_WINDOW, ...app.window } },\n page,\n networkTimeout: { request: 60000, uploadFile: 60000, connectSocket: 60000, downloadFile: 60000, ...app.networkTimeout },\n ext: app.ext || {},\n extAppid: app.extAppid || '',\n plugins: app.plugins || {},\n mainPlugins: { ...app.plugins },\n useExtendedLib: app.useExtendedLib || {},\n requiredPrivateInfos: app.requiredPrivateInfos || [],\n __warning__: '',\n entryPagePath: entryPage + '.html',\n tabBar,\n isLazyLoad: false,\n lazyCodeLoading: undefined,\n // 以下字段由开发者工具在运行时注入\n appname: appid,\n accountInfo: { appId: appid, icon: '', nickname: appid },\n appLaunchInfo: { scene: 1001, path: entryPage, query: {} },\n envVersion: 'develop',\n platform: 'devtools',\n env: { USER_DATA_PATH: 'http://usr' },\n isPluginMiniProgram: false,\n wxAppInfo: { maxRequestConcurrent: 10, maxUploadConcurrent: 10, maxDownloadConcurrent: 10, maxWebsocketConnect: 5 },\n deviceinfo: {}\n };\n}\n","/**\n * 小程序项目结构解析\n *\n * 负责读取 app.json / 页面与组件 json,并按开发者工具的规则做预处理:\n * - app.json 的全局 usingComponents 展开到每个页面和组件(miniprogram_npm 内除外)\n * - 每个页面/组件 json 都保证有 usingComponents 字段\n */\n\nimport fs from 'fs';\nimport path from 'path';\n\nexport interface AppJson {\n pages: string[];\n entryPagePath?: string;\n window?: Record<string, any>;\n tabBar?: { list: Array<{ pagePath: string; text?: string; iconPath?: string; selectedIconPath?: string }>; [key: string]: any };\n subPackages?: Array<{ root: string; pages: string[]; independent?: boolean; [key: string]: any }>;\n subpackages?: AppJson['subPackages'];\n usingComponents?: Record<string, string>;\n workers?: string | { path: string };\n [key: string]: any;\n}\n\nexport interface UnitJson {\n usingComponents: Record<string, string>;\n componentGenerics?: Record<string, boolean | { default?: string }>;\n component?: boolean;\n [key: string]: any;\n}\n\nconst IGNORED_DIRS = new Set(['node_modules', '.git', '.idea', '.vscode']);\n// wxa.setup.js 是 wxa-runner 的启动前脚本,不属于小程序\nconst PROJECT_FILES = new Set(['project.config.json', 'project.private.config.json', 'sitemap.json', 'app.json', 'wxa.setup.js']);\n\nexport class MiniProgram {\n /** 小程序代码根目录(已考虑 project.config.json 的 miniprogramRoot) */\n readonly root: string;\n readonly appid: string;\n\n constructor(readonly projectPath: string) {\n const projectConfig = readJsonFile(path.join(projectPath, 'project.config.json')) || {};\n this.root = path.resolve(projectPath, projectConfig.miniprogramRoot || '');\n this.appid = projectConfig.appid || 'touristappid';\n\n if (!fs.existsSync(path.join(this.root, 'app.json'))) {\n throw new Error(`${this.root} 下没有 app.json,不是有效的小程序目录`);\n }\n }\n\n readAppJson(): AppJson {\n const app = readJsonFile(path.join(this.root, 'app.json'));\n if (!app || !Array.isArray(app.pages) || app.pages.length === 0) {\n throw new Error('app.json 缺少 pages 配置');\n }\n return app;\n }\n\n subPackages(app: AppJson) {\n return app.subPackages || app.subpackages || [];\n }\n\n /** 主包页面 + 分包页面(带 root 前缀) */\n allPages(app: AppJson): string[] {\n const pages = [...app.pages];\n for (const pkg of this.subPackages(app)) {\n for (const page of pkg.pages) {\n const full = path.posix.join(pkg.root, page);\n if (!pages.includes(full)) pages.push(full);\n }\n }\n return pages;\n }\n\n /** 读取并预处理页面/组件 json,id 不带扩展名 */\n readUnitJson(id: string, app: AppJson): UnitJson {\n const json: UnitJson = { ...(readJsonFile(path.join(this.root, id + '.json')) || {}) };\n json.usingComponents = { ...(json.usingComponents || {}) };\n\n if (!id.startsWith('miniprogram_npm/') && !this.isInIndependentSubPackage(id, app)) {\n for (const [tag, ref] of Object.entries(app.usingComponents || {})) {\n if (json.usingComponents[tag]) continue;\n if (ref.startsWith('/') || ref.startsWith('plugin://')) {\n json.usingComponents[tag] = ref;\n } else {\n const relative = path.posix.relative(path.posix.dirname(id), ref);\n json.usingComponents[tag] = relative.startsWith('.') ? relative : './' + relative;\n }\n }\n }\n return json;\n }\n\n /**\n * 解析组件引用到组件路径(不带扩展名);插件组件或找不到时返回 null。\n * 规则:/ 开头为根目录绝对路径;否则先按相对路径找,再沿目录向上查找 miniprogram_npm。\n */\n resolveComponent(fromId: string, ref: string): string | null {\n if (!ref || ref.startsWith('plugin://') || ref.startsWith('plugin-private://')) return null;\n\n const fromDir = path.posix.dirname(fromId);\n const candidates: string[] = [];\n\n if (ref.startsWith('/')) {\n candidates.push(ref.slice(1));\n } else {\n candidates.push(path.posix.join(fromDir, ref));\n if (!ref.startsWith('.')) {\n let dir = fromDir;\n for (;;) {\n candidates.push(path.posix.join(dir === '.' ? '' : dir, 'miniprogram_npm', ref));\n if (dir === '.' || dir === '') break;\n dir = path.posix.dirname(dir);\n }\n }\n }\n\n for (const candidate of candidates) {\n const normalized = path.posix.normalize(candidate);\n for (const id of [normalized, path.posix.join(normalized, 'index')]) {\n if (fs.existsSync(path.join(this.root, id + '.json'))) return id;\n }\n }\n return null;\n }\n\n /** 收集所有页面以及它们(递归)引用到的自定义组件 */\n collectUnits(app: AppJson): { pages: string[]; components: string[] } {\n const pages = this.allPages(app);\n const components: string[] = [];\n const visited = new Set<string>(pages);\n\n const visit = (id: string) => {\n const json = this.readUnitJson(id, app);\n const refs = Object.values(json.usingComponents);\n for (const generic of Object.values(json.componentGenerics || {})) {\n if (typeof generic === 'object' && generic.default) refs.push(generic.default);\n }\n for (const ref of refs) {\n const component = this.resolveComponent(id, ref);\n if (component && !visited.has(component)) {\n visited.add(component);\n components.push(component);\n visit(component);\n }\n }\n };\n\n pages.forEach(visit);\n return { pages, components };\n }\n\n /** 列出代码根目录下指定扩展名的文件,返回 posix 风格的相对路径 */\n listFiles(extensions: string[], excludeDirs: string[] = []): string[] {\n const result: string[] = [];\n const excluded = new Set(excludeDirs.map(dir => dir.replace(/^\\/|\\/$/g, '')));\n\n const walk = (dir: string, rel: string) => {\n for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {\n if (entry.name.startsWith('.') || IGNORED_DIRS.has(entry.name)) continue;\n const childRel = rel ? `${rel}/${entry.name}` : entry.name;\n if (entry.isDirectory()) {\n if (!excluded.has(childRel)) walk(path.join(dir, entry.name), childRel);\n } else if (extensions.some(ext => entry.name.endsWith(ext))) {\n if (!rel && PROJECT_FILES.has(entry.name)) continue;\n result.push(childRel);\n }\n }\n };\n\n walk(this.root, '');\n return result.sort();\n }\n\n exists(relPath: string): boolean {\n return fs.existsSync(path.join(this.root, relPath));\n }\n\n readText(relPath: string): string {\n return fs.readFileSync(path.join(this.root, relPath), 'utf8');\n }\n\n private isInIndependentSubPackage(id: string, app: AppJson): boolean {\n return this.subPackages(app).some(pkg => pkg.independent && id.startsWith(pkg.root.replace(/\\/?$/, '/')));\n }\n}\n\nfunction readJsonFile(file: string): any {\n if (!fs.existsSync(file)) return null;\n const text = fs.readFileSync(file, 'utf8');\n try {\n return JSON.parse(text);\n } catch (error) {\n throw new Error(`${file} 不是合法的 JSON:${(error as Error).message}`);\n }\n}\n","/**\n * 宿主、逻辑层、渲染层的 HTML\n */\n\nimport { BuildResult, toJs } from '../project/AppBuilder';\n\nexport function hostHtml(): string {\n return `<!DOCTYPE html>\n<html lang=\"zh-CN\">\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n <title>wxa-runner</title>\n</head>\n<body>\n <div id=\"wxa-root\"></div>\n <script src=\"/__wxa__/runtime/host.js\"></script>\n</body>\n</html>`;\n}\n\n/**\n * 打开基础库的热更新开关(runtime/host/hmr.ts 依赖它):Page / Component / 模板注册时登记更新回调。\n * 必须紧跟在基础库之后、业务代码注册之前执行;只在监听文件变化时打开\n */\nfunction enableHotUpdate(hot: boolean): string {\n return hot\n ? `<script>if (typeof __virtualDOM__ !== 'undefined' && __virtualDOM__.enableUpdateWxAppCode) __virtualDOM__.enableUpdateWxAppCode();</script>\\n`\n : '';\n}\n\n/**\n * isolated:新版基础库(3.7.1 起)。逻辑层 iframe 只是主上下文,业务代码由基础库通过 WeixinJSContext\n * 放进子上下文执行(见 runtime/context.ts),逻辑层就绪的通知也由那边发出\n */\nexport function appserviceHtml(\n build: BuildResult,\n launch: { path: string; query: Record<string, string> },\n options: { hot: boolean; isolated?: boolean }\n): string {\n const wxConfig = {\n ...build.wxConfig,\n appLaunchInfo: { scene: 1001, path: launch.path, query: launch.query },\n ...(options.isolated ? { isIsolateContext: true } : {})\n };\n const v = build.version;\n const moduleUrls = build.modules.map(id => `/__wxa__/module/${encodeURI(id)}?v=${v}`);\n const bootstrapUrl = `/__wxa__/service-bootstrap.js?v=${v}`;\n\n if (options.isolated) {\n const scripts = [...moduleUrls, bootstrapUrl];\n return `<!DOCTYPE html>\n<html>\n<head>\n<meta charset=\"utf-8\">\n<title>appservice</title>\n<script>var __wxConfig = ${toJs(wxConfig)};</script>\n<script src=\"/__wxa__/runtime/frame.js\" data-kind=\"service\" data-app-service=\"${escapeHtml(JSON.stringify(scripts))}\"${options.hot ? ' data-hot=\"1\"' : ''}></script>\n<script src=\"/__baselib__/WAServiceMainContext.js\"></script>\n</head>\n<body></body>\n</html>`;\n }\n\n const modules = moduleUrls.map(src => `<script src=\"${src}\"></script>`).join('\\n');\n return `<!DOCTYPE html>\n<html>\n<head>\n<meta charset=\"utf-8\">\n<title>appservice</title>\n<script>var __wxConfig = ${toJs(wxConfig)};</script>\n<script src=\"/__wxa__/runtime/frame.js\" data-kind=\"service\"></script>\n<script src=\"/__baselib__/WAService.js\"></script>\n${enableHotUpdate(options.hot)}<script>\nif (typeof define !== 'function') {\n console.error('[wxa-runner] 基础库没有完成初始化(define 不存在),当前基础库版本可能不受支持');\n}\n</script>\n${modules}\n<script src=\"${bootstrapUrl}\"></script>\n<script>parent.__wxaHost.frameLoaded('service', 0);</script>\n</head>\n<body></body>\n</html>`;\n}\n\n/**\n * 主上下文通过 WeixinJSContext 创建的子上下文(runtime/context.ts)。\n * empty 对应 allocEmpty:不预先加载 WASubContext.js,由基础库随后 loadLibFiles\n */\nexport function subcontextHtml(id: number, empty: boolean): string {\n return `<!DOCTYPE html>\n<html>\n<head>\n<meta charset=\"utf-8\">\n<title>subcontext ${id}</title>\n<script src=\"/__wxa__/runtime/frame.js\" data-kind=\"service\" data-context-id=\"${id}\"></script>\n${empty ? '' : '<script src=\"/__baselib__/WASubContext.js\"></script>\\n'}<script>parent.__wxaContexts.loaded(${id});</script>\n</head>\n<body></body>\n</html>`;\n}\n\nexport function webviewHtml(build: BuildResult, pagePath: string, webviewId: number, options: { hot: boolean }): string {\n const pageConfig = build.wxConfig.page?.[pagePath + '.html'];\n const window = { ...build.wxConfig.global?.window, ...pageConfig?.window };\n const wxConfig = { ...build.wxConfig, window };\n const id = Number(webviewId) || 0;\n\n return `<!DOCTYPE html>\n<html>\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no\">\n<title>${escapeHtml(pagePath)}</title>\n<!-- 小程序渲染层的默认字号是 16px;不声明的话会继承浏览器设置里的默认字号。放在最前面,基础库和页面样式都能覆盖它 -->\n<style>html{font-size:16px}</style>\n<!-- 真机的滚动条是不占布局的浮层;桌面浏览器的常规滚动条和自定义 ::-webkit-scrollbar 会挤窄页面,一律隐藏。\n scrollbar-width 优先于 ::-webkit-scrollbar,页面样式不会再把滚动条加回来 -->\n<style>*{scrollbar-width:none}::-webkit-scrollbar{display:none}</style>\n<script>var __wxConfig = ${toJs(wxConfig)};</script>\n<script src=\"/__wxa__/runtime/frame.js\" data-kind=\"webview\" data-webview-id=\"${id}\"></script>\n<script src=\"/__baselib__/WAWebview.js\"></script>\n${enableHotUpdate(options.hot)}<script src=\"/__wxa__/page-frame.js?v=${build.version}\"></script>\n${window.disableScroll ? '<style>body{overflow-y:hidden}</style>' : ''}\n</head>\n<body>\n<script>\n(function () {\n var path = ${toJs(pagePath)};\n if (typeof __wxAppCode__[path + '.wxss'] === 'function') __wxAppCode__[path + '.wxss']();\n var generateFunc = $gwx('./' + path + '.wxml');\n if (generateFunc) {\n document.dispatchEvent(new CustomEvent('generateFuncReady', { detail: { generateFunc: generateFunc } }));\n } else {\n document.body.innerText = './' + path + '.wxml not found';\n console.error('[wxa-runner] ./' + path + '.wxml not found');\n }\n parent.__wxaHost.frameLoaded('webview', ${id});\n})();\n</script>\n</body>\n</html>`;\n}\n\nfunction escapeHtml(text: string): string {\n return text.replace(/[&<>\"]/g, ch => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '\"': '&quot;' }[ch]!));\n}\n","/**\n * 把基础库脚本加入 Chrome DevTools 的忽略列表(source map 的 x_google_ignoreList),调试时只看业务代码\n *\n * 新版基础库用 BaseConsole 包装 console 来收集日志,业务代码的 console.log 在 DevTools 里显示的来源\n * 成了这层包装(3.17 起基础库自己把它命名为 __WXAPPLIB_IGNORE__,就是留给调试工具跳过的)。\n * 忽略列表里的帧会被 DevTools 跳过:控制台的来源链接、调用栈、单步调试都落到业务代码上。\n * source map 只在打开 DevTools 时才会请求,不影响运行。\n *\n * 映射是恒等的:原始代码就是脚本本身,每行按固定间隔打点;从忽略列表里移除后仍能在基础库里调试,\n * 位置误差不超过一个间隔\n */\n\n/** 打点间隔(列):基础库压缩成很长的行,逐字符映射太大 */\nconst STRIDE = 100;\n\nconst BASE64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';\n\n/** @param source 原始代码的地址,相对 source map 的地址解析 */\nexport function ignoreListSourceMap(code: string, source: string): string {\n let previousColumn = 0;\n const mappings = code.split('\\n').map((line, index) => {\n const segments: string[] = [];\n for (let column = 0; column === 0 || column < line.length; column += STRIDE) {\n // 各字段都是相对上一个打点的增量:生成列(每行从 0 重新算)、源文件、源行、源列\n const sourceLine = column === 0 && index > 0 ? 1 : 0;\n segments.push(vlq(column === 0 ? 0 : STRIDE) + vlq(0) + vlq(sourceLine) + vlq(column - previousColumn));\n previousColumn = column;\n }\n return segments.join(',');\n });\n return JSON.stringify({ version: 3, sources: [source], names: [], mappings: mappings.join(';'), x_google_ignoreList: [0] });\n}\n\nfunction vlq(value: number): string {\n let rest = value < 0 ? (-value << 1) | 1 : value << 1;\n let out = '';\n do {\n let digit = rest & 31;\n rest >>>= 5;\n if (rest > 0) digit |= 32;\n out += BASE64[digit];\n } while (rest > 0);\n return out;\n}\n","/**\n * 网络代理:浏览器端的 wx.request 经由这里转发到真实服务器\n *\n * - 绕开 CORS\n * - 小程序设置的请求头放在 x-wxa-headers(JSON)里传过来,\n * 这样 cookie / referer / user-agent 等浏览器禁止设置的头也能带上\n * - 响应头和 set-cookie 以 URL 编码的 JSON 返回,浏览器端可以完整读取\n */\n\nimport type { Request, Response } from 'express';\n\n// fetch 不允许或无意义的请求头\nconst DROPPED_REQUEST_HEADERS = new Set(['host', 'connection', 'content-length', 'transfer-encoding', 'keep-alive', 'upgrade', 'expect']);\n// 响应体已被解压/重新分块,这些头不再成立\nconst DROPPED_RESPONSE_HEADERS = new Set(['content-encoding', 'content-length', 'transfer-encoding', 'connection', 'set-cookie']);\n\nexport async function handleProxy(req: Request, res: Response) {\n const target = String(req.query.url || '');\n if (!/^https?:\\/\\//i.test(target)) {\n res.status(400).set('x-wxa-proxy-error', '1').send(`invalid url \"${target}\"`);\n return;\n }\n\n let requestHeaders: Record<string, string> = {};\n try {\n requestHeaders = JSON.parse(req.get('x-wxa-headers') || '{}');\n } catch {\n // 头格式错误时按无自定义头处理\n }\n const headers: Record<string, string> = {};\n for (const [key, value] of Object.entries(requestHeaders)) {\n if (!DROPPED_REQUEST_HEADERS.has(key.toLowerCase()) && value !== undefined && value !== null) {\n headers[key] = String(value);\n }\n }\n // 上传文件时 multipart 的 boundary 只有浏览器生成的 content-type 里才有\n const browserContentType = req.get('content-type') || '';\n if (browserContentType.startsWith('multipart/form-data')) {\n for (const key of Object.keys(headers)) {\n if (key.toLowerCase() === 'content-type') delete headers[key];\n }\n headers['content-type'] = browserContentType;\n }\n\n const hasBody = !['GET', 'HEAD'].includes(req.method) && Buffer.isBuffer(req.body) && req.body.length > 0;\n\n let upstream: globalThis.Response;\n try {\n upstream = await fetch(target, {\n method: req.method,\n headers,\n body: hasBody ? req.body : undefined,\n redirect: 'follow'\n });\n } catch (error) {\n const cause = (error as any)?.cause?.message;\n res.status(502).set('x-wxa-proxy-error', '1').send(cause || (error as Error).message);\n return;\n }\n\n const responseHeaders: Record<string, string> = {};\n upstream.headers.forEach((value, key) => {\n responseHeaders[key] = value;\n });\n const setCookies = typeof upstream.headers.getSetCookie === 'function' ? upstream.headers.getSetCookie() : [];\n\n res.status(upstream.status);\n upstream.headers.forEach((value, key) => {\n if (!DROPPED_RESPONSE_HEADERS.has(key)) res.setHeader(key, value);\n });\n res.setHeader('x-wxa-headers', encodeURIComponent(JSON.stringify(responseHeaders)));\n res.setHeader('x-wxa-set-cookie', encodeURIComponent(JSON.stringify(setCookies)));\n res.send(Buffer.from(await upstream.arrayBuffer()));\n}\n","/**\n * 热更新分流:文件改动能否在不刷新整页的情况下应用(浏览器端应用见 runtime/host/hmr.ts)\n */\n\nexport interface HotUpdatePlan {\n /** 模板要更新的页面和组件(不带扩展名) */\n templates: string[];\n /** 逻辑要更新的页面和组件(不带扩展名) */\n scripts: string[];\n /** 改动的样式文件(不带扩展名),包括 app.wxss 和被 @import 的文件 */\n styles: string[];\n}\n\n/**\n * @param files 相对小程序根目录的改动文件\n * @returns null 表示需要整页刷新:只要有一个文件既不是样式,也不是页面或组件自己的 WXML / JS\n */\nexport function planHotUpdate(files: string[], units: { pages: string[]; components: string[] }): HotUpdatePlan | null {\n const owners = new Set([...units.pages, ...units.components]);\n const plan: HotUpdatePlan = { templates: [], scripts: [], styles: [] };\n\n for (const file of files) {\n const id = file.replace(/\\.(wxml|js|wxss)$/, '');\n const target = file.endsWith('.wxss')\n ? plan.styles\n : !owners.has(id) ? null : file.endsWith('.wxml') ? plan.templates : file.endsWith('.js') ? plan.scripts : null;\n if (!target) return null;\n if (!target.includes(id)) target.push(id);\n }\n return plan;\n}\n","/**\n * 真实登录(--real-login):用本机开发者工具的登录态,向微信换取真实的 wx.login code 和手机号\n *\n * POST /login → { code }\n * POST /phone-number → { data, encryptedData, iv },code 在 data(JSON 字符串)里,与基础库的约定一致\n *\n * 失败时返回 500 和 { error: 原因 }。开发者 ticket 只留在 Node 进程里,接口只接受本机访问,\n * 并校验 Host 头防止 DNS rebinding。\n */\n\nimport express, { NextFunction, Request, Response, Router } from 'express';\nimport { DeveloperTicket, findDeveloperTicket, TICKET_ENV } from '@open-wxa/wxa-runner-extractor';\n\nconst DEV_LOGIC = 'https://servicewechat.com/wxa-dev-logic';\n/** 开发者工具请求这些接口时带的版本号 */\nconst CLIENT_VERSION = '1021810240';\nconst UPSTREAM_TIMEOUT = 10_000;\nconst LOOPBACK_ADDRESSES = ['127.0.0.1', '::1', '::ffff:127.0.0.1'];\nconst LOOPBACK_HOSTS = ['localhost', '127.0.0.1', '[::1]'];\n\nconst ERROR_HINTS: Record<number, string> = {\n 40029: '(登录态已失效,请在微信开发者工具里重新登录)',\n 85023: '(当前微信账号不是该小程序的开发者)'\n};\n\n/**\n * 取出可用的 ticket\n *\n * @throws 没有登录态或已过期时,消息是给用户看的处理办法\n */\nexport function requireUsableTicket(ticket: DeveloperTicket | null): string {\n if (!ticket) throw new Error(`未找到微信开发者工具的登录态,请登录开发者工具,或设置环境变量 ${TICKET_ENV}`);\n if (ticket.expiredAt !== undefined && ticket.expiredAt <= Date.now()) {\n throw new Error('微信开发者工具的登录态已过期,请打开开发者工具刷新登录态');\n }\n return ticket.newticket;\n}\n\n/**\n * @param findTicket 每次请求都重新查找:开发者工具重新登录、切换账号后不用重启服务器\n */\nexport function realLoginRouter(appid: string, findTicket: () => DeveloperTicket | null = findDeveloperTicket): Router {\n const callDevLogic = async (endpoint: string, body: object) => {\n const query = new URLSearchParams({ appid, newticket: requireUsableTicket(findTicket()), clientversion: CLIENT_VERSION });\n const response = await fetch(`${DEV_LOGIC}/${endpoint}?${query}`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(body),\n signal: AbortSignal.timeout(UPSTREAM_TIMEOUT)\n }).catch((error: Error & { cause?: Error }) => {\n // Node 的 fetch 只报 fetch failed,具体原因(DNS、连接被拒等)在 cause 里\n throw new Error(error.name === 'TimeoutError'\n ? `请求微信服务器超时(${UPSTREAM_TIMEOUT / 1000}s)`\n : `请求微信服务器失败:${error.cause?.message ?? error.message}`);\n });\n if (!response.ok) throw new Error(`微信服务器返回 HTTP ${response.status}`);\n return response.json();\n };\n\n const router = express.Router();\n router.use(onlyLoopback);\n\n router.post('/login', respond(async () => {\n const body = await callDevLogic('jslogin', { scope: ['snsapi_base'] });\n const errcode = body.baseresponse?.errcode;\n if (errcode !== 0 || !body.code) {\n throw new Error(`微信登录失败 ${errcode}: ${body.baseresponse?.errmsg || '未知错误'}${ERROR_HINTS[errcode] ?? ''}`);\n }\n return { code: body.code };\n }));\n\n router.post('/phone-number', respond(async () => {\n const body = await callDevLogic('jsgetuserwxphone', { appid });\n // 结果码在 jsapi_baseresponse 里,不是 jslogin 那样的 baseresponse\n const errcode = body.jsapi_baseresponse?.errcode ?? body.BaseResponse?.Ret;\n if (errcode !== 0 || typeof body.data !== 'string') {\n const errmsg = body.jsapi_baseresponse?.errmsg || body.BaseResponse?.ErrMsg?.String || '未知错误';\n throw new Error(`获取手机号失败 ${errcode}: ${errmsg}${ERROR_HINTS[errcode] ?? ''}`);\n }\n // data 原样转交:基础库从 JSON.parse(data) 里取 code 和 request_id\n const { encryptedData, iv } = JSON.parse(body.data);\n return { data: body.data, encryptedData, iv };\n }));\n\n return router;\n}\n\nfunction onlyLoopback(req: Request, res: Response, next: NextFunction) {\n // req.hostname 去掉端口时能正确处理 IPv6 的 [::1]:3000\n if (!LOOPBACK_ADDRESSES.includes(req.socket.remoteAddress || '') || !LOOPBACK_HOSTS.includes(req.hostname)) {\n res.status(403).json({ error: '真实登录接口只允许本机访问' });\n return;\n }\n next();\n}\n\nfunction respond(action: () => Promise<object>) {\n return async (_req: Request, res: Response) => {\n try {\n res.json(await action());\n } catch (error) {\n res.status(500).json({ error: (error as Error).message });\n }\n };\n}\n","/**\n * setup 文件的发现、编译与环境变量(浏览器端执行见 runtime/host/setup.ts)\n */\n\nimport fs from 'fs';\nimport path from 'path';\nimport { build, Plugin } from 'esbuild';\n\nexport const SETUP_FILE_NAMES = ['wxa.setup.ts', 'wxa.setup.js', 'wxa.setup.mjs'];\nconst SETUP_MODULE = '@open-wxa/wxa-runner-core/setup';\nconst ENV_PREFIX = 'WXA_';\n\n/** 项目目录(project.config.json 所在目录)下约定名称的 setup 文件 */\nexport function findSetupFile(projectPath: string): string | null {\n for (const name of SETUP_FILE_NAMES) {\n const file = path.join(projectPath, name);\n if (fs.existsSync(file)) return file;\n }\n return null;\n}\n\n/** 只暴露 WXA_ 前缀的变量:setup 跑在浏览器里,其余环境变量可能是不该外泄的密钥 */\nexport function collectSetupEnv(env: NodeJS.ProcessEnv): Record<string, string> {\n return Object.fromEntries(\n Object.entries(env).filter((entry): entry is [string, string] => entry[0].startsWith(ENV_PREFIX) && entry[1] !== undefined)\n );\n}\n\n/**\n * 把 setup 文件连同它 import 的文件打成一个浏览器 ES 模块\n *\n * 用户的小程序项目里通常没装本包,对它的引用直接换成运行时自带的实现。\n *\n * @throws 语法错误、import 找不到等编译错误,消息里带相对 setup 文件目录的位置\n */\nexport async function bundleSetup(file: string): Promise<string> {\n const result = await build({\n entryPoints: [file],\n absWorkingDir: path.dirname(file),\n bundle: true,\n format: 'esm',\n platform: 'browser',\n target: 'es2020',\n write: false,\n logLevel: 'silent',\n sourcemap: 'inline',\n plugins: [builtinSetupModule()]\n });\n return result.outputFiles[0].text;\n}\n\nfunction builtinSetupModule(): Plugin {\n return {\n name: 'wxa-setup-module',\n setup(pluginBuild) {\n pluginBuild.onResolve({ filter: new RegExp(`^${SETUP_MODULE}$`) }, () => ({ path: SETUP_MODULE, namespace: 'wxa-setup' }));\n pluginBuild.onLoad({ filter: /.*/, namespace: 'wxa-setup' }, () => ({\n // 与 src/setup.ts 的 defineSetup 一致:只为类型提示,运行时原样返回\n contents: 'export function defineSetup(setup) { return setup; }',\n loader: 'js'\n }));\n }\n };\n}\n","/**\n * 文件监听:把开发者一次保存引起的文件变化合并成一批报告\n */\n\nimport { createHash } from 'crypto';\nimport fs from 'fs';\nimport path from 'path';\nimport chokidar from 'chokidar';\n\nexport interface FileChanges {\n /** 相对小程序根目录、以 / 分隔的路径 */\n files: string[];\n /** 有文件新增或删除:页面、模块列表可能变了,只能整页刷新 */\n structural: boolean;\n}\n\nexport interface ProjectWatcher {\n /** 初次扫描完成;此前已存在的文件不会作为改动报告 */\n ready: Promise<void>;\n close(): Promise<void>;\n}\n\nconst DEBOUNCE = 120;\nconst IGNORED = /(^|[/\\\\])(node_modules|\\.git)([/\\\\]|$)/;\nconst FINGERPRINTED = /\\.(js|mjs|ts|json|wxml|wxss|wxs)$/;\n\n/**\n * @param root 报告的路径相对这个目录\n * @param paths 要监听的目录和文件,可以在 root 之外\n * @param onChange 变化停下 120ms 后调用。内容没变的代码文件不算改动:\n * 构建工具(如 uni-app 的监听构建)每次保存都会重写全部产物\n */\nexport function watchProject(root: string, paths: string[], onChange: (changes: FileChanges) => void): ProjectWatcher {\n let changed = new Set<string>();\n let structural = false;\n let timer: ReturnType<typeof setTimeout> | null = null;\n // 初次扫描时记下每个代码文件的内容指纹\n const fingerprints = new Map<string, string>();\n let scanned = false;\n\n const watcher = chokidar.watch(paths, { ignored: IGNORED });\n const ready = new Promise<void>(resolve => {\n watcher.on('ready', () => {\n scanned = true;\n resolve();\n });\n });\n\n const flush = () => {\n const changes = { files: [...changed], structural };\n changed = new Set();\n structural = false;\n onChange(changes);\n };\n\n watcher.on('all', (event, file) => {\n const unchanged = updateFingerprint(fingerprints, file, event);\n if (!scanned || unchanged) return;\n if (event !== 'change') structural = true;\n changed.add(path.relative(root, file).split(path.sep).join('/'));\n if (timer) clearTimeout(timer);\n timer = setTimeout(flush, DEBOUNCE);\n });\n\n return { ready, close: () => watcher.close() };\n}\n\n/** 记录文件的内容指纹;返回 true 表示内容与上次相同。图片等其他文件不读取,一律视为有变化 */\nfunction updateFingerprint(fingerprints: Map<string, string>, file: string, event: string): boolean {\n if (event === 'unlink') {\n fingerprints.delete(file);\n return false;\n }\n if ((event !== 'add' && event !== 'change') || !FINGERPRINTED.test(file)) return false;\n let next: string;\n try {\n next = createHash('sha1').update(fs.readFileSync(file)).digest('hex');\n } catch {\n return false;\n }\n const previous = fingerprints.get(file);\n fingerprints.set(file, next);\n return previous === next;\n}\n"],"mappings":"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","names":["fs","path","express","findDeveloperTicket","result","build","fs","path","fs","path","path","findDeveloperTicket","build","express","fs"]}
1
+ {"version":3,"sources":["../src/server/DevServer.ts","../src/project/AppBuilder.ts","../src/project/wxConfig.ts","../src/project/MiniProgram.ts","../src/server/html.ts","../src/server/ignoreList.ts","../src/server/proxy.ts","../src/server/hmr.ts","../src/server/realLogin.ts","../src/server/setup.ts","../src/server/watcher.ts"],"sourcesContent":["/**\n * 开发服务器\n *\n * / 宿主页面\n * /__wxa__/app-info.json 构建信息(__wxConfig、页面列表、设备参数、setup)\n * /__wxa__/setup.js 编译后的 setup 文件\n * /__wxa__/appservice.html 逻辑层\n * /__wxa__/webview/<page>.html 渲染层(路径与页面一致,wxml 里的相对资源路径因此能正确解析)\n * /__wxa__/module/<file>.js 单个业务 JS 模块\n * /__wxa__/service-templates.js、page-templates.js 两层的模板(热更新时重新下发)\n * /__wxa__/page-styles.js 渲染层的样式(热更新时重新下发)\n * /__baselib__/* 官方基础库\n * /__wxa__/proxy?url= 网络代理\n * /__wxa__/tmp 临时文件:POST ?type=png 保存,返回可直接访问的地址;对该地址 HEAD 查大小、DELETE 删除\n * /__wxa__/region/<kind> 地区选择器用的行政区划数据(districts / streets),取自本机开发者工具\n * /__wxa__/api/* 真实登录(--real-login):wx.login 的 code、手机号,只允许本机访问\n * /__wxa__/ws 文件变化通知:热更新(hot)或整页刷新(reload)\n * 其余路径 小程序目录下的静态资源\n */\n\nimport { randomUUID } from 'crypto';\nimport fs from 'fs';\nimport http from 'http';\nimport path from 'path';\nimport express, { Express, NextFunction, Request, Response } from 'express';\nimport { WebSocket, WebSocketServer } from 'ws';\nimport { findDeveloperTicket, findRegionDataFile, resolveBaseLib, ResolvedBaseLib, usesIsolatedContext } from '@open-wxa/wxa-runner-extractor';\nimport { AppBuilder, BuildResult } from '../project/AppBuilder';\nimport { MiniProgram } from '../project/MiniProgram';\nimport type { AppInfo, DeviceProfile } from '../runtime/types';\nimport { appserviceHtml, hostHtml, subcontextHtml, webviewHtml } from './html';\nimport { ignoreListSourceMap } from './ignoreList';\nimport { handleProxy } from './proxy';\nimport { planHotUpdate } from './hmr';\nimport { realLoginRouter, requireUsableTicket } from './realLogin';\nimport { bundleSetup, collectSetupEnv, findSetupFile } from './setup';\nimport { ProjectWatcher, watchProject } from './watcher';\n\nexport interface DevServerOptions {\n projectPath: string;\n /** 默认 3000,传 0 使用随机端口 */\n port?: number;\n host?: string;\n /** 指定基础库版本,默认使用本机已安装的最新兼容版本 */\n baseLibVersion?: string;\n baseLibCacheDir?: string;\n watch?: boolean;\n device?: Partial<DeviceProfile>;\n /**\n * 小程序启动前执行的 setup 文件;默认使用项目目录下的 wxa.setup.ts / .js / .mjs,传 false 不使用\n * 环境变量在构造时读取,只暴露 WXA_ 前缀的变量\n */\n setup?: string | false;\n /** 浏览器端运行时(host.js / frame.js)所在目录,默认取构建产物 */\n runtimeDir?: string;\n /** 使用本机微信开发者工具登录态获取真实 code 和手机号 */\n realLogin?: boolean;\n logger?: Pick<Console, 'log' | 'warn' | 'error'>;\n}\n\nexport const DEFAULT_DEVICE: DeviceProfile = {\n model: 'iPhone X',\n screenWidth: 375,\n screenHeight: 812,\n pixelRatio: 3,\n statusBarHeight: 44,\n safeAreaBottom: 34,\n system: 'iOS 16.0',\n platform: 'devtools'\n};\n\nexport class DevServer {\n readonly project: MiniProgram;\n readonly builder: AppBuilder;\n readonly baseLib: ResolvedBaseLib;\n /** 生效的 setup 文件绝对路径,没有时为 null */\n readonly setupFile: string | null;\n private readonly setupEnv: Record<string, string>;\n private readonly device: DeviceProfile;\n private readonly logger: Pick<Console, 'log' | 'warn' | 'error'>;\n private readonly app: Express;\n private server: http.Server | null = null;\n private sockets: WebSocketServer | null = null;\n private watcher: ProjectWatcher | null = null;\n private address = '';\n /** 运行时生成的临时文件(canvas 导出、拍照等),随服务器进程存在 */\n private readonly tempFiles = new Map<string, Buffer>();\n\n constructor(private readonly options: DevServerOptions) {\n this.logger = options.logger || console;\n this.project = new MiniProgram(path.resolve(options.projectPath));\n this.baseLib = resolveBaseLib({ version: options.baseLibVersion, cacheDir: options.baseLibCacheDir });\n if (!this.baseLib.compatible) {\n this.logger.warn(`[wxa-runner] 基础库 ${this.baseLib.version} 高于已验证的版本范围,可能无法正常运行`);\n }\n if (options.realLogin) {\n try {\n requireUsableTicket(findDeveloperTicket());\n } catch (error) {\n this.logger.warn(`[wxa-runner] 真实登录暂不可用:${(error as Error).message}`);\n }\n }\n this.device = { ...DEFAULT_DEVICE, ...options.device };\n this.builder = new AppBuilder(this.project);\n this.setupFile = resolveSetupFile(options.setup, this.project.projectPath);\n this.setupEnv = collectSetupEnv(process.env);\n this.app = this.createApp();\n }\n\n get url(): string {\n return this.address;\n }\n\n async start(): Promise<string> {\n // 先构建一次:编译错误在终端里立即可见\n try {\n const build = await this.builder.build();\n this.logger.log(`[wxa-runner] 编译完成:${build.pages.length} 个页面,${build.components.length} 个组件,${build.durationMs}ms`);\n } catch (error) {\n this.logger.error(`[wxa-runner] 编译失败:\\n${(error as Error).message}`);\n }\n\n const server = http.createServer(this.app);\n this.sockets = new WebSocketServer({ noServer: true });\n server.on('upgrade', (req, socket, head) => {\n if (req.url?.startsWith('/__wxa__/ws')) {\n this.sockets!.handleUpgrade(req, socket, head, ws => this.sockets!.emit('connection', ws, req));\n } else {\n socket.destroy();\n }\n });\n\n const host = this.options.host || 'localhost';\n await new Promise<void>((resolve, reject) => {\n server.once('error', reject);\n server.listen(this.options.port ?? 3000, host, () => resolve());\n });\n this.server = server;\n\n const { port } = server.address() as { port: number };\n this.address = `http://${host}:${port}`;\n\n if (this.options.watch !== false) this.startWatching();\n return this.address;\n }\n\n async close() {\n await this.watcher?.close();\n this.sockets?.clients.forEach(client => client.terminate());\n this.sockets?.close();\n const server = this.server;\n this.server = null;\n if (server) {\n // 浏览器和 fetch 的 keep-alive 连接会让 close() 一直等到超时\n server.closeAllConnections();\n await new Promise<void>(resolve => server.close(() => resolve()));\n }\n }\n\n private createApp(): Express {\n const app = express();\n const root = this.project.root;\n const noStore = (res: Response) => res.setHeader('Cache-Control', 'no-store');\n\n app.get('/', (_req, res) => {\n noStore(res);\n res.type('html').send(hostHtml());\n });\n\n app.get('/favicon.ico', (_req, res) => {\n res.status(204).end();\n });\n\n app.get('/__wxa__/app-info.json', async (_req, res) => {\n noStore(res);\n const base = { appid: this.project.appid, baseLibVersion: this.baseLib.version, device: this.device, realLogin: this.options.realLogin === true };\n try {\n const build = await this.builder.build();\n const info: AppInfo = { ...base, version: build.version, wxConfig: build.wxConfig, pages: build.pages };\n if (this.setupFile) info.setup = await this.setupInfo(this.setupFile);\n res.json(info);\n } catch (error) {\n res.json({ ...base, version: 0, wxConfig: {}, pages: [], buildError: (error as Error).message });\n }\n });\n\n app.get('/__wxa__/setup.js', async (_req, res) => {\n noStore(res);\n if (!this.setupFile) {\n res.status(404).type('text').send('没有 setup 文件');\n return;\n }\n try {\n res.type('js').send(await bundleSetup(this.setupFile));\n } catch (error) {\n res.status(500).type('text').send((error as Error).message);\n }\n });\n\n app.get('/__wxa__/appservice.html', this.withBuild((build, req, res) => {\n const launchPath = String(req.query.path || '').replace(/^\\//, '');\n const launch = build.pages.includes(launchPath)\n ? { path: launchPath, query: Object.fromEntries(new URLSearchParams(String(req.query.query || ''))) }\n : { path: build.pages[0], query: {} };\n res.type('html').send(appserviceHtml(build, launch, { hot: this.hotEnabled, isolated: usesIsolatedContext(this.baseLib.version) }));\n }));\n\n app.get('/__wxa__/subcontext.html', (req, res) => {\n const id = Number(req.query.id);\n if (!Number.isInteger(id) || id <= 0) {\n res.status(400).type('text').send('子上下文 id 无效');\n return;\n }\n res.type('html').send(subcontextHtml(id, req.query.empty === '1'));\n });\n\n app.get(/^\\/__wxa__\\/webview\\/(.+)\\.html$/, this.withBuild((build, req, res, next) => {\n const pagePath = decodeURIComponent(req.params[0]);\n if (!build.pages.includes(pagePath)) return next();\n res.type('html').send(webviewHtml(build, pagePath, Number(req.query.id), { hot: this.hotEnabled }));\n }));\n\n app.get(/^\\/__wxa__\\/module\\/(.+\\.js)$/, this.withBuild((build, req, res) => {\n const id = decodeURIComponent(req.params[0]);\n if (!build.modules.includes(id)) {\n res.status(404).type('js').send(`console.error(${JSON.stringify(`[wxa-runner] 模块 ${id} 不存在`)})`);\n return;\n }\n res.type('js').send(this.builder.getModuleScript(id));\n }));\n\n app.get('/__wxa__/service-bootstrap.js', this.withBuild((build, _req, res) => {\n res.type('js').send(build.serviceBootstrap);\n }));\n\n app.get('/__wxa__/service-templates.js', this.withBuild((build, _req, res) => {\n res.type('js').send(build.serviceTemplates);\n }));\n\n app.get('/__wxa__/page-templates.js', this.withBuild((build, _req, res) => {\n res.type('js').send(build.pageTemplates);\n }));\n\n app.get('/__wxa__/page-styles.js', this.withBuild((build, _req, res) => {\n res.type('js').send(build.pageStyles);\n }));\n\n app.get('/__wxa__/page-frame.js', this.withBuild((build, _req, res) => {\n res.type('js').send(build.pageFrame);\n }));\n\n app.use('/__wxa__/runtime', express.static(this.runtimeDir(), { cacheControl: false }));\n // 基础库脚本加入 DevTools 的忽略列表,原始代码就是脚本本身(换个地址,免得和脚本混为一个文件)\n const sourceMaps = new Map<string, string>();\n app.get(/^\\/__baselib__\\/(.+\\.js)\\.map$/, (req, res, next) => {\n const file = path.resolve(this.baseLib.dir, req.params[0]);\n if (!file.startsWith(this.baseLib.dir + path.sep) || !fs.existsSync(file)) return next();\n if (!sourceMaps.has(file)) sourceMaps.set(file, ignoreListSourceMap(fs.readFileSync(file, 'utf8'), `${path.basename(file)}?original`));\n res.type('json').send(sourceMaps.get(file));\n });\n app.use('/__baselib__', express.static(this.baseLib.dir, {\n maxAge: '1d',\n setHeaders: (res, file) => {\n if (file.endsWith('.js')) res.setHeader('SourceMap', `${path.basename(file)}.map`);\n }\n }));\n app.all('/__wxa__/proxy', express.raw({ type: () => true, limit: '100mb' }), handleProxy);\n\n // 返回完整的 http 地址:image、video 组件只原样使用 http(s) 地址,其余的会被当成页面相对路径\n app.post('/__wxa__/tmp', express.raw({ type: () => true, limit: '100mb' }), (req, res) => {\n const type = String(req.query.type || 'png');\n if (!/^[a-z0-9]+$/i.test(type)) {\n res.status(400).type('text').send(`非法的文件类型 ${type}`);\n return;\n }\n const name = `${randomUUID()}.${type}`;\n this.tempFiles.set(name, Buffer.isBuffer(req.body) ? req.body : Buffer.alloc(0));\n res.json({ url: `${req.protocol}://${req.get('host')}/__wxa__/tmp/${name}` });\n });\n app.get('/__wxa__/region/:kind', (req, res) => {\n const kind = req.params.kind;\n const file = kind === 'districts' || kind === 'streets' ? findRegionDataFile(kind) : null;\n if (!file) {\n res.status(404).end();\n return;\n }\n res.type('text/plain; charset=utf-8').sendFile(file, { maxAge: '1d' });\n });\n\n app.get('/__wxa__/tmp/:name', (req, res) => {\n const data = this.tempFiles.get(req.params.name);\n if (!data) {\n res.status(404).end();\n return;\n }\n res.type(path.extname(req.params.name)).send(data);\n });\n app.delete('/__wxa__/tmp/:name', (req, res) => {\n res.status(this.tempFiles.delete(req.params.name) ? 204 : 404).end();\n });\n\n if (this.options.realLogin) app.use('/__wxa__/api', realLoginRouter(this.project.appid));\n\n // 渲染层页面路径下的相对资源(如 ../../images/a.png)\n app.use('/__wxa__/webview', express.static(root));\n app.use(express.static(root));\n\n app.use((error: Error, _req: Request, res: Response, _next: NextFunction) => {\n this.logger.error('[wxa-runner]', error);\n res.status(500).type('text').send(error.message);\n });\n\n return app;\n }\n\n /** 先编译一遍 setup:编译错误随 app-info 交给宿主显示,而不是让浏览器 import 失败 */\n private async setupInfo(file: string): Promise<NonNullable<AppInfo['setup']>> {\n try {\n await bundleSetup(file);\n return { env: this.setupEnv };\n } catch (error) {\n return { env: this.setupEnv, error: (error as Error).message };\n }\n }\n\n /** 需要构建结果的路由;构建失败时返回错误文本 */\n private withBuild(handler: (build: BuildResult, req: Request, res: Response, next: NextFunction) => void) {\n return async (req: Request, res: Response, next: NextFunction) => {\n res.setHeader('Cache-Control', 'no-store');\n let build: BuildResult;\n try {\n build = await this.builder.build();\n } catch (error) {\n res.status(500).type('text').send((error as Error).message);\n return;\n }\n try {\n handler(build, req, res, next);\n } catch (error) {\n next(error);\n }\n };\n }\n\n private runtimeDir(): string {\n const candidates = [\n this.options.runtimeDir,\n path.join(__dirname, 'runtime'),\n path.join(__dirname, '../runtime'),\n path.join(__dirname, '../../dist/runtime')\n ].filter((dir): dir is string => !!dir);\n const found = candidates.find(dir => fs.existsSync(path.join(dir, 'host.js')));\n if (!found) {\n throw new Error('找不到浏览器端运行时(host.js),请先执行 pnpm build');\n }\n return found;\n }\n\n /** 只在监听文件变化时打开基础库的热更新开关,跑测试(watch: false)时运行环境与真机一致 */\n private get hotEnabled(): boolean {\n return this.options.watch !== false;\n }\n\n private startWatching() {\n // 上次编译失败时页面上显示着错误,修好后整页刷新才能清掉\n let lastBuildFailed = false;\n\n // 项目目录下的 setup 文件不一定在小程序根目录(miniprogramRoot)里\n const watched = [this.project.root];\n if (this.setupFile && path.relative(this.project.root, this.setupFile).startsWith('..')) watched.push(this.setupFile);\n\n this.watcher = watchProject(this.project.root, watched, async ({ files, structural }) => {\n const reloadOnly = structural || lastBuildFailed;\n this.builder.invalidate(files);\n const label = files.length > 3 ? `${files.slice(0, 3).join(', ')} 等 ${files.length} 个文件` : files.join(', ');\n\n try {\n const build = await this.builder.build();\n lastBuildFailed = false;\n const plan = reloadOnly ? null : planHotUpdate(files, build);\n this.logger.log(`[wxa-runner] ${label} 已更新,重新编译 ${build.durationMs}ms${plan ? ',热更新' : ''}`);\n this.broadcast(plan ? { type: 'hot', version: build.version, ...plan } : { type: 'reload' });\n } catch (error) {\n lastBuildFailed = true;\n const message = (error as Error).message;\n this.logger.error(`[wxa-runner] 编译失败:\\n${message}`);\n this.broadcast({ type: 'error', message });\n }\n });\n }\n\n private broadcast(message: Record<string, unknown>) {\n const data = JSON.stringify(message);\n this.sockets?.clients.forEach(client => {\n if (client.readyState === WebSocket.OPEN) client.send(data);\n });\n }\n}\n\nfunction resolveSetupFile(option: string | false | undefined, projectPath: string): string | null {\n if (option === false) return null;\n if (!option) return findSetupFile(projectPath);\n const file = path.resolve(option);\n if (!fs.existsSync(file)) throw new Error(`找不到 setup 文件:${file}`);\n return file;\n}\n","/**\n * 把小程序项目构建成官方基础库能直接加载的形式\n *\n * 逻辑层(appservice):\n * 1. 裁剪版 $gwx(wcc -xc),基础库用它了解组件的节点结构\n * 2. 每个 JS 文件单独一个 define(\"x.js\", fn) 脚本(便于在 DevTools 里按原文件调试)\n * 3. bootstrap:注册 __wxAppCode__ 的 json/wxml(serviceTemplates,热更新时单独下发),\n * require(\"app.js\"),再依次 require 组件和页面\n *\n * 渲染层(page-frame):\n * 1. 完整版 $gwx(wcc -cc)与每个页面/组件的 __wxAppCode__ json / wxml(pageTemplates)\n * 2. wcsc 的公共样式运行时 + app.wxss,与每个页面/组件的 __wxAppCode__ wxss(pageStyles)\n * 两部分在热更新时分别下发\n * 样式和模板里的 env(safe-area-inset-*) 改写成 CSS 变量,见 rewriteSafeAreaInsets\n */\n\nimport { compileWxml, compileWxss, ComponentTags } from '@open-wxa/wxa-runner-compiler';\nimport { transformSync } from 'esbuild';\nimport { AppJson, MiniProgram } from './MiniProgram';\nimport { createWxConfig } from './wxConfig';\n\nexport interface BuildResult {\n version: number;\n wxConfig: Record<string, any>;\n /** 所有 JS 模块 id(如 \"pages/index/index.js\"),逻辑层按此顺序加载 define 脚本 */\n modules: string[];\n /** 逻辑层的 $gwx 与 __wxAppCode__ json/wxml,不含 require */\n serviceTemplates: string;\n serviceBootstrap: string;\n pageFrame: string;\n /** 渲染层的 $gwx 与 __wxAppCode__ json/wxml,不含样式(热更新时单独下发) */\n pageTemplates: string;\n /** 渲染层的样式:公共样式立即注入,页面和组件的样式注册到 __wxAppCode__(热更新时单独下发) */\n pageStyles: string;\n pages: string[];\n components: string[];\n durationMs: number;\n}\n\n/** define 包装函数的参数:屏蔽浏览器全局对象,与开发者工具一致 */\nconst MODULE_PARAMS = [\n 'require', 'module', 'exports', 'window', 'document', 'frames', 'self', 'location', 'navigator',\n 'localStorage', 'history', 'Caches', 'screen', 'alert', 'confirm', 'prompt', 'XMLHttpRequest',\n 'WebSocket', 'Reporter', 'webkit', 'WeixinJSCore'\n].join(', ');\n\n/** 行首的 import / export 语句(不匹配 import() 动态导入) */\nconst ESM_SYNTAX = /^\\s*(import\\s*[\\w*{'\"]|export\\s+(default|const|let|var|function|class|async|\\{|\\*))/m;\n\ntype ChangeKind = 'config' | 'wxml' | 'wxss' | 'js';\n\nexport class AppBuilder {\n private version = 0;\n /** invalidate 标记过的变化次数;每次构建记下开始时覆盖到第几次 */\n private changes = 0;\n private pending: { covers: number; result: Promise<BuildResult> } | null = null;\n private last: BuildResult | null = null;\n private dirty = new Set<ChangeKind>(['config', 'wxml', 'wxss', 'js']);\n private wxmlCache: { full: string; cut: string } | null = null;\n private wxssCache: { comm: string; pages: Record<string, string> } | null = null;\n\n constructor(readonly project: MiniProgram) {}\n\n /** 根据变化的文件标记需要重新构建的部分;返回 true 表示需要刷新 */\n invalidate(changedFiles: string[] = []): boolean {\n if (changedFiles.length === 0) {\n ['config', 'wxml', 'wxss', 'js'].forEach(kind => this.dirty.add(kind as ChangeKind));\n this.changes++;\n return true;\n }\n\n let relevant = false;\n for (const file of changedFiles) {\n const kind = classifyFile(file);\n if (!kind) continue;\n relevant = true;\n this.dirty.add(kind);\n // json 决定组件关系,wxml/wxss 的编译参数都依赖它\n if (kind === 'config') {\n this.dirty.add('wxml');\n this.dirty.add('wxss');\n }\n }\n if (relevant) this.changes++;\n return relevant;\n }\n\n /**\n * 返回包含调用前所有变化的构建结果。并发调用共享同一次构建;\n * 进行中的构建开始得比最近一次变化早时(可能已读到旧文件),等它结束后再构建一次\n */\n async build(): Promise<BuildResult> {\n const wanted = this.changes;\n for (;;) {\n if (!this.pending && this.dirty.size === 0 && this.last) return this.last;\n if (!this.pending) {\n const result = this.doBuild().finally(() => {\n this.pending = null;\n });\n this.pending = { covers: this.changes, result };\n }\n const { covers, result } = this.pending;\n const built = await result;\n if (covers >= wanted) return built;\n }\n }\n\n /**\n * 单个 JS 模块的 define 包装\n *\n * 包装头和代码第一行写在同一行,行号与源文件一致。使用 import/export 的文件\n * 先转换为 CommonJS(对应开发者工具的「ES6 转 ES5」),并内联 source map。\n */\n getModuleScript(id: string): string {\n let code = this.project.readText(id);\n if (ESM_SYNTAX.test(code)) {\n try {\n code = transformSync(code, {\n format: 'cjs',\n loader: 'js',\n target: 'es2020',\n sourcefile: id,\n sourcemap: 'inline'\n }).code;\n } catch (error) {\n const message = `[wxa-runner] ${id} 转换失败:${(error as Error).message}`;\n return `console.error(${JSON.stringify(message)});\\n`;\n }\n }\n return `define(${JSON.stringify(id)}, function(${MODULE_PARAMS}){${code}\\n});\\n`;\n }\n\n private async doBuild(): Promise<BuildResult> {\n const start = Date.now();\n const dirty = new Set(this.dirty);\n this.dirty.clear();\n\n try {\n const app = this.project.readAppJson();\n const { pages, components } = this.project.collectUnits(app);\n const units = [...components, ...pages];\n const unitJson = new Map(units.map(id => [id, this.project.readUnitJson(id, app)]));\n\n const [wxml, wxss] = await Promise.all([\n dirty.has('wxml') || !this.wxmlCache ? this.compileTemplates(units, unitJson) : this.wxmlCache,\n dirty.has('wxss') || !this.wxssCache ? this.compileStyles(units) : this.wxssCache\n ]);\n this.wxmlCache = wxml;\n this.wxssCache = wxss;\n\n const workersDir = typeof app.workers === 'string' ? app.workers : app.workers?.path;\n const modules = this.project.listFiles(['.js'], workersDir ? [workersDir] : []);\n\n const serviceTemplates = this.createTemplates(app, units, unitJson, wxml.cut);\n const pageTemplates = this.createTemplates(app, units, unitJson, wxml.full);\n const pageStyles = createStyles(units, wxss);\n const result: BuildResult = {\n version: ++this.version,\n wxConfig: createWxConfig({\n app,\n pages,\n unitJson,\n appid: this.project.appid,\n subPackages: this.project.subPackages(app)\n }),\n modules,\n serviceTemplates,\n serviceBootstrap: this.createServiceBootstrap(serviceTemplates, units, modules),\n pageFrame: `${pageTemplates}\\n${pageStyles}`,\n pageTemplates,\n pageStyles,\n pages,\n components,\n durationMs: Date.now() - start\n };\n this.last = result;\n return result;\n } catch (error) {\n // 构建失败时保留脏标记,下次请求重新尝试\n dirty.forEach(kind => this.dirty.add(kind));\n throw error;\n }\n }\n\n private async compileTemplates(units: string[], unitJson: Map<string, any>) {\n const files = this.project.listFiles(['.wxml', '.wxs']).map(f => './' + f);\n const components: ComponentTags[] = units\n .filter(id => this.project.exists(id + '.wxml'))\n .map(id => {\n const json = unitJson.get(id);\n return { path: id, tags: Object.keys({ ...json.usingComponents, ...(json.componentGenerics || {}) }) };\n });\n\n const cwd = this.project.root;\n const [full, cut] = await Promise.all([\n compileWxml({ cwd, files, components, cut: false }),\n compileWxml({ cwd, files, components, cut: true })\n ]);\n // 只有渲染层的完整版带内联 style\n return { full: rewriteSafeAreaInsets(full), cut };\n }\n\n /** 与开发者工具一致,项目里的 wxss 全部交给 wcsc:被 @import 的文件(如 uni-app 的 common/main.wxss)也要在列表里 */\n private async compileStyles(units: string[]) {\n const pageFiles = units.filter(id => this.project.exists(id + '.wxss')).map(id => `./${id}.wxss`);\n const commonFiles = this.project.listFiles(['.wxss']).map(file => `./${file}`).filter(file => !pageFiles.includes(file));\n const { comm, pages } = await compileWxss({ cwd: this.project.root, pageFiles, commonFiles });\n return {\n comm: rewriteSafeAreaInsets(comm),\n pages: Object.fromEntries(Object.entries(pages).map(([file, css]) => [file, rewriteSafeAreaInsets(css)]))\n };\n }\n\n /** $gwx 加上各页面、组件的 json / wxml 注册;逻辑层用裁剪版 $gwx,渲染层用完整版 */\n private createTemplates(app: AppJson, units: string[], unitJson: Map<string, any>, gwx: string): string {\n const lines: string[] = [gwx, `__wxAppCode__[\"app.json\"] = ${toJs(app)};`];\n\n for (const id of units) {\n lines.push(`__wxAppCode__[${toJs(id + '.json')}] = ${toJs(unitJson.get(id))};`);\n if (this.project.exists(id + '.wxml')) {\n lines.push(`__wxAppCode__[${toJs(id + '.wxml')}] = $gwx(${toJs('./' + id + '.wxml')});`);\n }\n }\n return lines.join('\\n');\n }\n\n private createServiceBootstrap(serviceTemplates: string, units: string[], modules: string[]): string {\n const lines: string[] = [serviceTemplates];\n\n if (modules.includes('app.js')) {\n lines.push(requireStatement('app.js', null));\n }\n for (const id of units) {\n if (modules.includes(id + '.js')) lines.push(requireStatement(id + '.js', id));\n }\n return lines.join('\\n');\n }\n}\n\nfunction createStyles(units: string[], wxss: { comm: string; pages: Record<string, string> }): string {\n const lines = [wxss.comm];\n for (const id of units) {\n const css = wxss.pages[id + '.wxss'];\n if (css) lines.push(`__wxAppCode__[${toJs(id + '.wxss')}] = ${css};`);\n }\n return lines.join('\\n');\n}\n\n/** 某个模块执行出错时只影响它自己,其余页面照常注册 */\nfunction requireStatement(file: string, route: string | null): string {\n const routeSetup = route\n ? `__wxRoute = ${toJs(route)}; __wxRouteBegin = true; __wxAppCurrentFile__ = ${toJs(file)};`\n : `__wxAppCurrentFile__ = ${toJs(file)};`;\n return `${routeSetup}\\ntry { require(${toJs(file)}); } catch (error) { console.error(${toJs('[wxa-runner] ' + file + ' 执行出错\\n')}, error); }`;\n}\n\n/**\n * env(safe-area-inset-*) / constant(safe-area-inset-*) → var(--wxa-safe-area-inset-*)\n *\n * 桌面浏览器里这些 env() 恒为 0,而真机上的值取决于页面窗口是否贴着屏幕边缘(tabBar 页面底部为 0),\n * 所以改成变量,由宿主按页面写入(PageView.setSafeAreaInsets)。只替换函数名和参数名,\n * fallback 原样保留——wcsc 会把其中的 rpx 拆成数组片段,不能整体解析。\n */\nexport function rewriteSafeAreaInsets(code: string): string {\n return code.replace(/\\b(?:env|constant)\\(\\s*safe-area-inset-(top|right|bottom|left)\\b/g, 'var(--wxa-safe-area-inset-$1');\n}\n\n/** JSON 序列化并转义 </script> 与行分隔符,结果可安全嵌入脚本 */\nexport function toJs(value: unknown): string {\n return JSON.stringify(value)\n .replace(/</g, '\\\\u003c')\n .replace(/\\u2028/g, '\\\\u2028')\n .replace(/\\u2029/g, '\\\\u2029');\n}\n\nfunction classifyFile(file: string): ChangeKind | null {\n if (file.endsWith('.json')) return 'config';\n if (file.endsWith('.wxml') || file.endsWith('.wxs')) return 'wxml';\n if (file.endsWith('.wxss')) return 'wxss';\n if (file.endsWith('.js')) return 'js';\n return null;\n}\n","/**\n * 生成基础库读取的 __wxConfig,字段与开发者工具保持一致\n */\n\nimport { AppJson, UnitJson } from './MiniProgram';\n\nexport const DEFAULT_WINDOW = {\n navigationBarBackgroundColor: '#000000',\n navigationBarTextStyle: 'white',\n navigationBarTitleText: '',\n backgroundColor: '#ffffff',\n backgroundTextStyle: 'dark',\n enablePullDownRefresh: false,\n onReachBottomDistance: 50\n};\n\nexport interface WxConfigInput {\n app: AppJson;\n pages: string[];\n unitJson: Map<string, UnitJson>;\n appid: string;\n subPackages: NonNullable<AppJson['subPackages']>;\n}\n\nexport function createWxConfig(input: WxConfigInput): Record<string, any> {\n const { app, pages, unitJson, appid } = input;\n const entryPage = app.entryPagePath || app.pages[0];\n\n const page: Record<string, any> = {};\n for (const id of pages) {\n const { singlePage, ...window } = unitJson.get(id) || { usingComponents: {} };\n page[id + '.html'] = { window, singlePage, renderer: window.renderer };\n }\n\n const tabBar = app.tabBar\n ? { ...app.tabBar, list: app.tabBar.list.map(item => ({ ...item, pagePath: item.pagePath + '.html' })) }\n : undefined;\n\n return {\n ...app,\n pages: app.pages,\n resizable: !!app.resizable,\n subPackages: input.subPackages.length ? input.subPackages : undefined,\n debug: !!app.debug,\n widgets: app.widgets || [],\n customClose: !!app.customClose,\n workers: app.workers || '',\n cloud: !!app.cloud,\n global: { window: { ...DEFAULT_WINDOW, ...app.window } },\n page,\n networkTimeout: { request: 60000, uploadFile: 60000, connectSocket: 60000, downloadFile: 60000, ...app.networkTimeout },\n ext: app.ext || {},\n extAppid: app.extAppid || '',\n plugins: app.plugins || {},\n mainPlugins: { ...app.plugins },\n useExtendedLib: app.useExtendedLib || {},\n requiredPrivateInfos: app.requiredPrivateInfos || [],\n __warning__: '',\n entryPagePath: entryPage + '.html',\n tabBar,\n isLazyLoad: false,\n lazyCodeLoading: undefined,\n // 以下字段由开发者工具在运行时注入\n appname: appid,\n accountInfo: { appId: appid, icon: '', nickname: appid },\n appLaunchInfo: { scene: 1001, path: entryPage, query: {} },\n envVersion: 'develop',\n platform: 'devtools',\n env: { USER_DATA_PATH: 'http://usr' },\n isPluginMiniProgram: false,\n wxAppInfo: { maxRequestConcurrent: 10, maxUploadConcurrent: 10, maxDownloadConcurrent: 10, maxWebsocketConnect: 5 },\n deviceinfo: {}\n };\n}\n","/**\n * 小程序项目结构解析\n *\n * 负责读取 app.json / 页面与组件 json,并按开发者工具的规则做预处理:\n * - app.json 的全局 usingComponents 展开到每个页面和组件(miniprogram_npm 内除外)\n * - 每个页面/组件 json 都保证有 usingComponents 字段\n */\n\nimport fs from 'fs';\nimport path from 'path';\n\nexport interface AppJson {\n pages: string[];\n entryPagePath?: string;\n window?: Record<string, any>;\n tabBar?: { list: Array<{ pagePath: string; text?: string; iconPath?: string; selectedIconPath?: string }>; [key: string]: any };\n subPackages?: Array<{ root: string; pages: string[]; independent?: boolean; [key: string]: any }>;\n subpackages?: AppJson['subPackages'];\n usingComponents?: Record<string, string>;\n workers?: string | { path: string };\n [key: string]: any;\n}\n\nexport interface UnitJson {\n usingComponents: Record<string, string>;\n componentGenerics?: Record<string, boolean | { default?: string }>;\n component?: boolean;\n [key: string]: any;\n}\n\nconst IGNORED_DIRS = new Set(['node_modules', '.git', '.idea', '.vscode']);\n// wxa.setup.js 是 wxa-runner 的启动前脚本,不属于小程序\nconst PROJECT_FILES = new Set(['project.config.json', 'project.private.config.json', 'sitemap.json', 'app.json', 'wxa.setup.js']);\n\nexport class MiniProgram {\n /** 小程序代码根目录(已考虑 project.config.json 的 miniprogramRoot) */\n readonly root: string;\n readonly appid: string;\n\n constructor(readonly projectPath: string) {\n const projectConfig = readJsonFile(path.join(projectPath, 'project.config.json')) || {};\n this.root = path.resolve(projectPath, projectConfig.miniprogramRoot || '');\n this.appid = projectConfig.appid || 'touristappid';\n\n if (!fs.existsSync(path.join(this.root, 'app.json'))) {\n throw new Error(`${this.root} 下没有 app.json,不是有效的小程序目录`);\n }\n }\n\n readAppJson(): AppJson {\n const app = readJsonFile(path.join(this.root, 'app.json'));\n if (!app || !Array.isArray(app.pages) || app.pages.length === 0) {\n throw new Error('app.json 缺少 pages 配置');\n }\n return app;\n }\n\n subPackages(app: AppJson) {\n return app.subPackages || app.subpackages || [];\n }\n\n /** 主包页面 + 分包页面(带 root 前缀) */\n allPages(app: AppJson): string[] {\n const pages = [...app.pages];\n for (const pkg of this.subPackages(app)) {\n for (const page of pkg.pages) {\n const full = path.posix.join(pkg.root, page);\n if (!pages.includes(full)) pages.push(full);\n }\n }\n return pages;\n }\n\n /** 读取并预处理页面/组件 json,id 不带扩展名 */\n readUnitJson(id: string, app: AppJson): UnitJson {\n const json: UnitJson = { ...(readJsonFile(path.join(this.root, id + '.json')) || {}) };\n json.usingComponents = { ...(json.usingComponents || {}) };\n\n if (!id.startsWith('miniprogram_npm/') && !this.isInIndependentSubPackage(id, app)) {\n for (const [tag, ref] of Object.entries(app.usingComponents || {})) {\n if (json.usingComponents[tag]) continue;\n if (ref.startsWith('/') || ref.startsWith('plugin://')) {\n json.usingComponents[tag] = ref;\n } else {\n const relative = path.posix.relative(path.posix.dirname(id), ref);\n json.usingComponents[tag] = relative.startsWith('.') ? relative : './' + relative;\n }\n }\n }\n return json;\n }\n\n /**\n * 解析组件引用到组件路径(不带扩展名);插件组件或找不到时返回 null。\n * 规则:/ 开头为根目录绝对路径;否则先按相对路径找,再沿目录向上查找 miniprogram_npm。\n */\n resolveComponent(fromId: string, ref: string): string | null {\n if (!ref || ref.startsWith('plugin://') || ref.startsWith('plugin-private://')) return null;\n\n const fromDir = path.posix.dirname(fromId);\n const candidates: string[] = [];\n\n if (ref.startsWith('/')) {\n candidates.push(ref.slice(1));\n } else {\n candidates.push(path.posix.join(fromDir, ref));\n if (!ref.startsWith('.')) {\n let dir = fromDir;\n for (;;) {\n candidates.push(path.posix.join(dir === '.' ? '' : dir, 'miniprogram_npm', ref));\n if (dir === '.' || dir === '') break;\n dir = path.posix.dirname(dir);\n }\n }\n }\n\n for (const candidate of candidates) {\n const normalized = path.posix.normalize(candidate);\n for (const id of [normalized, path.posix.join(normalized, 'index')]) {\n if (fs.existsSync(path.join(this.root, id + '.json'))) return id;\n }\n }\n return null;\n }\n\n /** 收集所有页面以及它们(递归)引用到的自定义组件 */\n collectUnits(app: AppJson): { pages: string[]; components: string[] } {\n const pages = this.allPages(app);\n const components: string[] = [];\n const visited = new Set<string>(pages);\n\n const visit = (id: string) => {\n const json = this.readUnitJson(id, app);\n const refs = Object.values(json.usingComponents);\n for (const generic of Object.values(json.componentGenerics || {})) {\n if (typeof generic === 'object' && generic.default) refs.push(generic.default);\n }\n for (const ref of refs) {\n const component = this.resolveComponent(id, ref);\n if (component && !visited.has(component)) {\n visited.add(component);\n components.push(component);\n visit(component);\n }\n }\n };\n\n pages.forEach(visit);\n return { pages, components };\n }\n\n /** 列出代码根目录下指定扩展名的文件,返回 posix 风格的相对路径 */\n listFiles(extensions: string[], excludeDirs: string[] = []): string[] {\n const result: string[] = [];\n const excluded = new Set(excludeDirs.map(dir => dir.replace(/^\\/|\\/$/g, '')));\n\n const walk = (dir: string, rel: string) => {\n for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {\n if (entry.name.startsWith('.') || IGNORED_DIRS.has(entry.name)) continue;\n const childRel = rel ? `${rel}/${entry.name}` : entry.name;\n if (entry.isDirectory()) {\n if (!excluded.has(childRel)) walk(path.join(dir, entry.name), childRel);\n } else if (extensions.some(ext => entry.name.endsWith(ext))) {\n if (!rel && PROJECT_FILES.has(entry.name)) continue;\n result.push(childRel);\n }\n }\n };\n\n walk(this.root, '');\n return result.sort();\n }\n\n exists(relPath: string): boolean {\n return fs.existsSync(path.join(this.root, relPath));\n }\n\n readText(relPath: string): string {\n return fs.readFileSync(path.join(this.root, relPath), 'utf8');\n }\n\n private isInIndependentSubPackage(id: string, app: AppJson): boolean {\n return this.subPackages(app).some(pkg => pkg.independent && id.startsWith(pkg.root.replace(/\\/?$/, '/')));\n }\n}\n\nfunction readJsonFile(file: string): any {\n if (!fs.existsSync(file)) return null;\n const text = fs.readFileSync(file, 'utf8');\n try {\n return JSON.parse(text);\n } catch (error) {\n throw new Error(`${file} 不是合法的 JSON:${(error as Error).message}`);\n }\n}\n","/**\n * 宿主、逻辑层、渲染层的 HTML\n */\n\nimport { BuildResult, toJs } from '../project/AppBuilder';\n\nexport function hostHtml(): string {\n return `<!DOCTYPE html>\n<html lang=\"zh-CN\">\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n <title>wxa-runner</title>\n</head>\n<body>\n <div id=\"wxa-root\"></div>\n <script src=\"/__wxa__/runtime/host.js\"></script>\n</body>\n</html>`;\n}\n\n/**\n * 打开基础库的热更新开关(runtime/host/hmr.ts 依赖它):Page / Component / 模板注册时登记更新回调。\n * 必须紧跟在基础库之后、业务代码注册之前执行;只在监听文件变化时打开\n */\nfunction enableHotUpdate(hot: boolean): string {\n return hot\n ? `<script>if (typeof __virtualDOM__ !== 'undefined' && __virtualDOM__.enableUpdateWxAppCode) __virtualDOM__.enableUpdateWxAppCode();</script>\\n`\n : '';\n}\n\n/**\n * isolated:新版基础库(3.7.1 起)。逻辑层 iframe 只是主上下文,业务代码由基础库通过 WeixinJSContext\n * 放进子上下文执行(见 runtime/context.ts),逻辑层就绪的通知也由那边发出\n */\nexport function appserviceHtml(\n build: BuildResult,\n launch: { path: string; query: Record<string, string> },\n options: { hot: boolean; isolated?: boolean }\n): string {\n const wxConfig = {\n ...build.wxConfig,\n appLaunchInfo: { scene: 1001, path: launch.path, query: launch.query },\n ...(options.isolated ? { isIsolateContext: true } : {})\n };\n const v = build.version;\n const moduleUrls = build.modules.map(id => `/__wxa__/module/${encodeURI(id)}?v=${v}`);\n const bootstrapUrl = `/__wxa__/service-bootstrap.js?v=${v}`;\n\n if (options.isolated) {\n const scripts = [...moduleUrls, bootstrapUrl];\n return `<!DOCTYPE html>\n<html>\n<head>\n<meta charset=\"utf-8\">\n<title>appservice</title>\n<script>var __wxConfig = ${toJs(wxConfig)};</script>\n<script src=\"/__wxa__/runtime/frame.js\" data-kind=\"service\" data-app-service=\"${escapeHtml(JSON.stringify(scripts))}\"${options.hot ? ' data-hot=\"1\"' : ''}></script>\n<script src=\"/__baselib__/WAServiceMainContext.js\"></script>\n</head>\n<body></body>\n</html>`;\n }\n\n const modules = moduleUrls.map(src => `<script src=\"${src}\"></script>`).join('\\n');\n return `<!DOCTYPE html>\n<html>\n<head>\n<meta charset=\"utf-8\">\n<title>appservice</title>\n<script>var __wxConfig = ${toJs(wxConfig)};</script>\n<script src=\"/__wxa__/runtime/frame.js\" data-kind=\"service\"></script>\n<script src=\"/__baselib__/WAService.js\"></script>\n${enableHotUpdate(options.hot)}<script>\nif (typeof define !== 'function') {\n console.error('[wxa-runner] 基础库没有完成初始化(define 不存在),当前基础库版本可能不受支持');\n}\n</script>\n${modules}\n<script src=\"${bootstrapUrl}\"></script>\n<script>parent.__wxaHost.frameLoaded('service', 0);</script>\n</head>\n<body></body>\n</html>`;\n}\n\n/**\n * 主上下文通过 WeixinJSContext 创建的子上下文(runtime/context.ts)。\n * empty 对应 allocEmpty:不预先加载 WASubContext.js,由基础库随后 loadLibFiles\n */\nexport function subcontextHtml(id: number, empty: boolean): string {\n return `<!DOCTYPE html>\n<html>\n<head>\n<meta charset=\"utf-8\">\n<title>subcontext ${id}</title>\n<script src=\"/__wxa__/runtime/frame.js\" data-kind=\"service\" data-context-id=\"${id}\"></script>\n${empty ? '' : '<script src=\"/__baselib__/WASubContext.js\"></script>\\n'}<script>parent.__wxaContexts.loaded(${id});</script>\n</head>\n<body></body>\n</html>`;\n}\n\nexport function webviewHtml(build: BuildResult, pagePath: string, webviewId: number, options: { hot: boolean }): string {\n const pageConfig = build.wxConfig.page?.[pagePath + '.html'];\n const window = { ...build.wxConfig.global?.window, ...pageConfig?.window };\n const wxConfig = { ...build.wxConfig, window };\n const id = Number(webviewId) || 0;\n\n return `<!DOCTYPE html>\n<html>\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no\">\n<title>${escapeHtml(pagePath)}</title>\n<!-- 小程序渲染层的默认字号是 16px;不声明的话会继承浏览器设置里的默认字号。放在最前面,基础库和页面样式都能覆盖它 -->\n<style>html{font-size:16px}</style>\n<!-- 真机的滚动条是不占布局的浮层;桌面浏览器的常规滚动条和自定义 ::-webkit-scrollbar 会挤窄页面,一律隐藏。\n scrollbar-width 优先于 ::-webkit-scrollbar,页面样式不会再把滚动条加回来 -->\n<style>*{scrollbar-width:none}::-webkit-scrollbar{display:none}</style>\n<script>var __wxConfig = ${toJs(wxConfig)};</script>\n<script src=\"/__wxa__/runtime/frame.js\" data-kind=\"webview\" data-webview-id=\"${id}\"></script>\n<script src=\"/__baselib__/WAWebview.js\"></script>\n${enableHotUpdate(options.hot)}<script src=\"/__wxa__/page-frame.js?v=${build.version}\"></script>\n${window.disableScroll ? '<style>body{overflow-y:hidden}</style>' : ''}\n</head>\n<body>\n<script>\n(function () {\n var path = ${toJs(pagePath)};\n if (typeof __wxAppCode__[path + '.wxss'] === 'function') __wxAppCode__[path + '.wxss']();\n var generateFunc = $gwx('./' + path + '.wxml');\n if (generateFunc) {\n document.dispatchEvent(new CustomEvent('generateFuncReady', { detail: { generateFunc: generateFunc } }));\n } else {\n document.body.innerText = './' + path + '.wxml not found';\n console.error('[wxa-runner] ./' + path + '.wxml not found');\n }\n parent.__wxaHost.frameLoaded('webview', ${id});\n})();\n</script>\n</body>\n</html>`;\n}\n\nfunction escapeHtml(text: string): string {\n return text.replace(/[&<>\"]/g, ch => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '\"': '&quot;' }[ch]!));\n}\n","/**\n * 把基础库脚本加入 Chrome DevTools 的忽略列表(source map 的 x_google_ignoreList),调试时只看业务代码\n *\n * 新版基础库用 BaseConsole 包装 console 来收集日志,业务代码的 console.log 在 DevTools 里显示的来源\n * 成了这层包装(3.17 起基础库自己把它命名为 __WXAPPLIB_IGNORE__,就是留给调试工具跳过的)。\n * 忽略列表里的帧会被 DevTools 跳过:控制台的来源链接、调用栈、单步调试都落到业务代码上。\n * source map 只在打开 DevTools 时才会请求,不影响运行。\n *\n * 映射是恒等的:原始代码就是脚本本身,每行按固定间隔打点;从忽略列表里移除后仍能在基础库里调试,\n * 位置误差不超过一个间隔\n */\n\n/** 打点间隔(列):基础库压缩成很长的行,逐字符映射太大 */\nconst STRIDE = 100;\n\nconst BASE64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';\n\n/** @param source 原始代码的地址,相对 source map 的地址解析 */\nexport function ignoreListSourceMap(code: string, source: string): string {\n let previousColumn = 0;\n const mappings = code.split('\\n').map((line, index) => {\n const segments: string[] = [];\n for (let column = 0; column === 0 || column < line.length; column += STRIDE) {\n // 各字段都是相对上一个打点的增量:生成列(每行从 0 重新算)、源文件、源行、源列\n const sourceLine = column === 0 && index > 0 ? 1 : 0;\n segments.push(vlq(column === 0 ? 0 : STRIDE) + vlq(0) + vlq(sourceLine) + vlq(column - previousColumn));\n previousColumn = column;\n }\n return segments.join(',');\n });\n return JSON.stringify({ version: 3, sources: [source], names: [], mappings: mappings.join(';'), x_google_ignoreList: [0] });\n}\n\nfunction vlq(value: number): string {\n let rest = value < 0 ? (-value << 1) | 1 : value << 1;\n let out = '';\n do {\n let digit = rest & 31;\n rest >>>= 5;\n if (rest > 0) digit |= 32;\n out += BASE64[digit];\n } while (rest > 0);\n return out;\n}\n","/**\n * 网络代理:浏览器端的 wx.request 经由这里转发到真实服务器\n *\n * - 绕开 CORS\n * - 小程序设置的请求头放在 x-wxa-headers(JSON)里传过来,\n * 这样 cookie / referer / user-agent 等浏览器禁止设置的头也能带上\n * - 响应头和 set-cookie 以 URL 编码的 JSON 返回,浏览器端可以完整读取\n */\n\nimport type { Request, Response } from 'express';\n\n// fetch 不允许或无意义的请求头\nconst DROPPED_REQUEST_HEADERS = new Set(['host', 'connection', 'content-length', 'transfer-encoding', 'keep-alive', 'upgrade', 'expect']);\n// 响应体已被解压/重新分块,这些头不再成立\nconst DROPPED_RESPONSE_HEADERS = new Set(['content-encoding', 'content-length', 'transfer-encoding', 'connection', 'set-cookie']);\n\nexport async function handleProxy(req: Request, res: Response) {\n const target = String(req.query.url || '');\n if (!/^https?:\\/\\//i.test(target)) {\n res.status(400).set('x-wxa-proxy-error', '1').send(`invalid url \"${target}\"`);\n return;\n }\n\n let requestHeaders: Record<string, string> = {};\n try {\n requestHeaders = JSON.parse(req.get('x-wxa-headers') || '{}');\n } catch {\n // 头格式错误时按无自定义头处理\n }\n const headers: Record<string, string> = {};\n for (const [key, value] of Object.entries(requestHeaders)) {\n if (!DROPPED_REQUEST_HEADERS.has(key.toLowerCase()) && value !== undefined && value !== null) {\n headers[key] = String(value);\n }\n }\n // 上传文件时 multipart 的 boundary 只有浏览器生成的 content-type 里才有\n const browserContentType = req.get('content-type') || '';\n if (browserContentType.startsWith('multipart/form-data')) {\n for (const key of Object.keys(headers)) {\n if (key.toLowerCase() === 'content-type') delete headers[key];\n }\n headers['content-type'] = browserContentType;\n }\n\n const hasBody = !['GET', 'HEAD'].includes(req.method) && Buffer.isBuffer(req.body) && req.body.length > 0;\n\n let upstream: globalThis.Response;\n try {\n upstream = await fetch(target, {\n method: req.method,\n headers,\n body: hasBody ? req.body : undefined,\n redirect: 'follow'\n });\n } catch (error) {\n const cause = (error as any)?.cause?.message;\n res.status(502).set('x-wxa-proxy-error', '1').send(cause || (error as Error).message);\n return;\n }\n\n const responseHeaders: Record<string, string> = {};\n upstream.headers.forEach((value, key) => {\n responseHeaders[key] = value;\n });\n const setCookies = typeof upstream.headers.getSetCookie === 'function' ? upstream.headers.getSetCookie() : [];\n\n res.status(upstream.status);\n upstream.headers.forEach((value, key) => {\n if (!DROPPED_RESPONSE_HEADERS.has(key)) res.setHeader(key, value);\n });\n res.setHeader('x-wxa-headers', encodeURIComponent(JSON.stringify(responseHeaders)));\n res.setHeader('x-wxa-set-cookie', encodeURIComponent(JSON.stringify(setCookies)));\n res.send(Buffer.from(await upstream.arrayBuffer()));\n}\n","/**\n * 热更新分流:文件改动能否在不刷新整页的情况下应用(浏览器端应用见 runtime/host/hmr.ts)\n */\n\nexport interface HotUpdatePlan {\n /** 模板要更新的页面和组件(不带扩展名) */\n templates: string[];\n /** 逻辑要更新的页面和组件(不带扩展名) */\n scripts: string[];\n /** 改动的样式文件(不带扩展名),包括 app.wxss 和被 @import 的文件 */\n styles: string[];\n}\n\n/**\n * @param files 相对小程序根目录的改动文件\n * @returns null 表示需要整页刷新:只要有一个文件既不是样式,也不是页面或组件自己的 WXML / JS\n */\nexport function planHotUpdate(files: string[], units: { pages: string[]; components: string[] }): HotUpdatePlan | null {\n const owners = new Set([...units.pages, ...units.components]);\n const plan: HotUpdatePlan = { templates: [], scripts: [], styles: [] };\n\n for (const file of files) {\n const id = file.replace(/\\.(wxml|js|wxss)$/, '');\n const target = file.endsWith('.wxss')\n ? plan.styles\n : !owners.has(id) ? null : file.endsWith('.wxml') ? plan.templates : file.endsWith('.js') ? plan.scripts : null;\n if (!target) return null;\n if (!target.includes(id)) target.push(id);\n }\n return plan;\n}\n","/**\n * 真实登录(--real-login):用本机开发者工具的登录态,向微信换取真实的 wx.login code 和手机号\n *\n * POST /login → { code }\n * POST /phone-number → { data, encryptedData, iv },code 在 data(JSON 字符串)里,与基础库的约定一致\n *\n * 失败时返回 500 和 { error: 原因 }。开发者 ticket 只留在 Node 进程里,接口只接受本机访问,\n * 并校验 Host 头防止 DNS rebinding。\n */\n\nimport express, { NextFunction, Request, Response, Router } from 'express';\nimport { DeveloperTicket, findDeveloperTicket, TICKET_ENV } from '@open-wxa/wxa-runner-extractor';\n\nconst DEV_LOGIC = 'https://servicewechat.com/wxa-dev-logic';\n/** 开发者工具请求这些接口时带的版本号 */\nconst CLIENT_VERSION = '1021810240';\nconst UPSTREAM_TIMEOUT = 10_000;\nconst LOOPBACK_ADDRESSES = ['127.0.0.1', '::1', '::ffff:127.0.0.1'];\nconst LOOPBACK_HOSTS = ['localhost', '127.0.0.1', '[::1]'];\n\nconst ERROR_HINTS: Record<number, string> = {\n 40029: '(登录态已失效,请在微信开发者工具里重新登录)',\n 85023: '(当前微信账号不是该小程序的开发者)'\n};\n\n/**\n * 取出可用的 ticket\n *\n * @throws 没有登录态或已过期时,消息是给用户看的处理办法\n */\nexport function requireUsableTicket(ticket: DeveloperTicket | null): string {\n if (!ticket) throw new Error(`未找到微信开发者工具的登录态,请登录开发者工具,或设置环境变量 ${TICKET_ENV}`);\n if (ticket.expiredAt !== undefined && ticket.expiredAt <= Date.now()) {\n throw new Error('微信开发者工具的登录态已过期,请打开开发者工具刷新登录态');\n }\n return ticket.newticket;\n}\n\n/**\n * @param findTicket 每次请求都重新查找:开发者工具重新登录、切换账号后不用重启服务器\n */\nexport function realLoginRouter(appid: string, findTicket: () => DeveloperTicket | null = findDeveloperTicket): Router {\n const callDevLogic = async (endpoint: string, body: object) => {\n const query = new URLSearchParams({ appid, newticket: requireUsableTicket(findTicket()), clientversion: CLIENT_VERSION });\n const response = await fetch(`${DEV_LOGIC}/${endpoint}?${query}`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(body),\n signal: AbortSignal.timeout(UPSTREAM_TIMEOUT)\n }).catch((error: Error & { cause?: Error }) => {\n // Node 的 fetch 只报 fetch failed,具体原因(DNS、连接被拒等)在 cause 里\n throw new Error(error.name === 'TimeoutError'\n ? `请求微信服务器超时(${UPSTREAM_TIMEOUT / 1000}s)`\n : `请求微信服务器失败:${error.cause?.message ?? error.message}`);\n });\n if (!response.ok) throw new Error(`微信服务器返回 HTTP ${response.status}`);\n return response.json();\n };\n\n const router = express.Router();\n router.use(onlyLoopback);\n\n router.post('/login', respond(async () => {\n const body = await callDevLogic('jslogin', { scope: ['snsapi_base'] });\n const errcode = body.baseresponse?.errcode;\n if (errcode !== 0 || !body.code) {\n throw new Error(`微信登录失败 ${errcode}: ${body.baseresponse?.errmsg || '未知错误'}${ERROR_HINTS[errcode] ?? ''}`);\n }\n return { code: body.code };\n }));\n\n router.post('/phone-number', respond(async () => {\n const body = await callDevLogic('jsgetuserwxphone', { appid });\n // 结果码在 jsapi_baseresponse 里,不是 jslogin 那样的 baseresponse\n const errcode = body.jsapi_baseresponse?.errcode ?? body.BaseResponse?.Ret;\n if (errcode !== 0 || typeof body.data !== 'string') {\n const errmsg = body.jsapi_baseresponse?.errmsg || body.BaseResponse?.ErrMsg?.String || '未知错误';\n throw new Error(`获取手机号失败 ${errcode}: ${errmsg}${ERROR_HINTS[errcode] ?? ''}`);\n }\n // data 原样转交:基础库从 JSON.parse(data) 里取 code 和 request_id\n const { encryptedData, iv } = JSON.parse(body.data);\n return { data: body.data, encryptedData, iv };\n }));\n\n return router;\n}\n\nfunction onlyLoopback(req: Request, res: Response, next: NextFunction) {\n // req.hostname 去掉端口时能正确处理 IPv6 的 [::1]:3000\n if (!LOOPBACK_ADDRESSES.includes(req.socket.remoteAddress || '') || !LOOPBACK_HOSTS.includes(req.hostname)) {\n res.status(403).json({ error: '真实登录接口只允许本机访问' });\n return;\n }\n next();\n}\n\nfunction respond(action: () => Promise<object>) {\n return async (_req: Request, res: Response) => {\n try {\n res.json(await action());\n } catch (error) {\n res.status(500).json({ error: (error as Error).message });\n }\n };\n}\n","/**\n * setup 文件的发现、编译与环境变量(浏览器端执行见 runtime/host/setup.ts)\n */\n\nimport fs from 'fs';\nimport path from 'path';\nimport { build, Plugin } from 'esbuild';\n\nexport const SETUP_FILE_NAMES = ['wxa.setup.ts', 'wxa.setup.js', 'wxa.setup.mjs'];\nconst SETUP_MODULE = '@open-wxa/wxa-runner-core/setup';\nconst ENV_PREFIX = 'WXA_';\n\n/** 项目目录(project.config.json 所在目录)下约定名称的 setup 文件 */\nexport function findSetupFile(projectPath: string): string | null {\n for (const name of SETUP_FILE_NAMES) {\n const file = path.join(projectPath, name);\n if (fs.existsSync(file)) return file;\n }\n return null;\n}\n\n/** 只暴露 WXA_ 前缀的变量:setup 跑在浏览器里,其余环境变量可能是不该外泄的密钥 */\nexport function collectSetupEnv(env: NodeJS.ProcessEnv): Record<string, string> {\n return Object.fromEntries(\n Object.entries(env).filter((entry): entry is [string, string] => entry[0].startsWith(ENV_PREFIX) && entry[1] !== undefined)\n );\n}\n\n/**\n * 把 setup 文件连同它 import 的文件打成一个浏览器 ES 模块\n *\n * 用户的小程序项目里通常没装本包,对它的引用直接换成运行时自带的实现。\n *\n * @throws 语法错误、import 找不到等编译错误,消息里带相对 setup 文件目录的位置\n */\nexport async function bundleSetup(file: string): Promise<string> {\n const result = await build({\n entryPoints: [file],\n absWorkingDir: path.dirname(file),\n bundle: true,\n format: 'esm',\n platform: 'browser',\n target: 'es2020',\n write: false,\n logLevel: 'silent',\n sourcemap: 'inline',\n plugins: [builtinSetupModule()]\n });\n return result.outputFiles[0].text;\n}\n\nfunction builtinSetupModule(): Plugin {\n return {\n name: 'wxa-setup-module',\n setup(pluginBuild) {\n pluginBuild.onResolve({ filter: new RegExp(`^${SETUP_MODULE}$`) }, () => ({ path: SETUP_MODULE, namespace: 'wxa-setup' }));\n pluginBuild.onLoad({ filter: /.*/, namespace: 'wxa-setup' }, () => ({\n // 与 src/setup.ts 的 defineSetup 一致:只为类型提示,运行时原样返回\n contents: 'export function defineSetup(setup) { return setup; }',\n loader: 'js'\n }));\n }\n };\n}\n","/**\n * 文件监听:把开发者一次保存引起的文件变化合并成一批报告\n */\n\nimport { createHash } from 'crypto';\nimport fs from 'fs';\nimport path from 'path';\nimport chokidar from 'chokidar';\n\nexport interface FileChanges {\n /** 相对小程序根目录、以 / 分隔的路径 */\n files: string[];\n /** 有文件新增或删除:页面、模块列表可能变了,只能整页刷新 */\n structural: boolean;\n}\n\nexport interface ProjectWatcher {\n /** 初次扫描完成;此前已存在的文件不会作为改动报告 */\n ready: Promise<void>;\n close(): Promise<void>;\n}\n\nconst DEBOUNCE = 120;\nconst IGNORED = /(^|[/\\\\])(node_modules|\\.git)([/\\\\]|$)/;\nconst FINGERPRINTED = /\\.(js|mjs|ts|json|wxml|wxss|wxs)$/;\n\n/**\n * @param root 报告的路径相对这个目录\n * @param paths 要监听的目录和文件,可以在 root 之外\n * @param onChange 变化停下 120ms 后调用。内容没变的代码文件不算改动:\n * 构建工具(如 uni-app 的监听构建)每次保存都会重写全部产物\n */\nexport function watchProject(root: string, paths: string[], onChange: (changes: FileChanges) => void): ProjectWatcher {\n let changed = new Set<string>();\n let structural = false;\n let timer: ReturnType<typeof setTimeout> | null = null;\n // 初次扫描时记下每个代码文件的内容指纹\n const fingerprints = new Map<string, string>();\n let scanned = false;\n\n const watcher = chokidar.watch(paths, { ignored: IGNORED });\n const ready = new Promise<void>(resolve => {\n watcher.on('ready', () => {\n scanned = true;\n resolve();\n });\n });\n\n const flush = () => {\n const changes = { files: [...changed], structural };\n changed = new Set();\n structural = false;\n onChange(changes);\n };\n\n watcher.on('all', (event, file) => {\n const unchanged = updateFingerprint(fingerprints, file, event);\n if (!scanned || unchanged) return;\n if (event !== 'change') structural = true;\n changed.add(path.relative(root, file).split(path.sep).join('/'));\n if (timer) clearTimeout(timer);\n timer = setTimeout(flush, DEBOUNCE);\n });\n\n return { ready, close: () => watcher.close() };\n}\n\n/** 记录文件的内容指纹;返回 true 表示内容与上次相同。图片等其他文件不读取,一律视为有变化 */\nfunction updateFingerprint(fingerprints: Map<string, string>, file: string, event: string): boolean {\n if (event === 'unlink') {\n fingerprints.delete(file);\n return false;\n }\n if ((event !== 'add' && event !== 'change') || !FINGERPRINTED.test(file)) return false;\n let next: string;\n try {\n next = createHash('sha1').update(fs.readFileSync(file)).digest('hex');\n } catch {\n return false;\n }\n const previous = fingerprints.get(file);\n fingerprints.set(file, next);\n return previous === next;\n}\n"],"mappings":"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","names":["fs","path","express","findDeveloperTicket","result","build","fs","path","fs","path","path","findDeveloperTicket","build","express","fs"]}
@@ -85,17 +85,32 @@
85
85
  }
86
86
 
87
87
  // src/runtime/host/api/tempFiles.ts
88
+ var TEMP_DIR = "/__wxa__/tmp";
88
89
  async function saveTempFile(data, type) {
89
- const res = await fetch(`/__wxa__/tmp?type=${encodeURIComponent(type)}`, { method: "POST", body: data });
90
+ const res = await fetch(`${TEMP_DIR}?type=${encodeURIComponent(type)}`, { method: "POST", body: data });
90
91
  if (!res.ok) throw new Error(await res.text());
91
92
  const { url } = await res.json();
92
93
  return url;
93
94
  }
95
+ function tempFileRootPath(url) {
96
+ const prefix = `${location.origin}${TEMP_DIR}/`;
97
+ return url.startsWith(prefix) ? url.slice(location.origin.length) : url;
98
+ }
94
99
  function tempFileType(name, mimeType, fallback) {
95
100
  const fromName = /\.([a-z\d]+)$/i.exec(name)?.[1];
96
101
  const fromType = /^[a-z]+\/([a-z\d]+)\s*(;|$)/i.exec(mimeType)?.[1];
97
102
  return (fromName || fromType || fallback).toLowerCase();
98
103
  }
104
+ function isTempFile(filePath) {
105
+ return typeof filePath === "string" && filePath.startsWith(`${location.origin}${TEMP_DIR}/`);
106
+ }
107
+ async function tempFileSize(url) {
108
+ const res = await fetch(url, { method: "HEAD" });
109
+ return res.ok ? Number(res.headers.get("content-length")) : null;
110
+ }
111
+ async function removeTempFile(url) {
112
+ await fetch(url, { method: "DELETE" });
113
+ }
99
114
 
100
115
  // src/runtime/host/api/media.ts
101
116
  var SOURCES = ["camera", "album"];
@@ -275,6 +290,9 @@
275
290
  unlink: (args, { api }) => guard(api, () => fs.remove(args.filePath, { dir: false })),
276
291
  rmdir: (args, { api }) => guard(api, () => fs.remove(args.dirPath, { dir: true, recursive: !!args.recursive })),
277
292
  getFileInfo: (args, { api }) => {
293
+ if (isTempFile(args.filePath)) {
294
+ return tempFileSize(args.filePath).then((size) => size === null ? fail(api, "file not exist") : ok(api, { size }));
295
+ }
278
296
  const node = fs.stat(args.filePath);
279
297
  return node ? ok(api, { size: node.data.length }) : fail(api, "file not exist");
280
298
  }
@@ -831,6 +849,50 @@
831
849
  return text.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
832
850
  }
833
851
 
852
+ // src/runtime/host/api/savedFiles.ts
853
+ var NAMESPACE = "savedFiles";
854
+ function createSavedFileApis(storage) {
855
+ const record = (filePath) => storage.get(NAMESPACE, filePath)?.data;
856
+ const liveRecord = async (filePath) => {
857
+ const saved = record(filePath);
858
+ if (!saved) return void 0;
859
+ if (await tempFileSize(filePath) !== null) return saved;
860
+ storage.remove(NAMESPACE, filePath);
861
+ return void 0;
862
+ };
863
+ return {
864
+ // 和真机一样:存成新文件,原来的临时文件随之删除
865
+ saveFile: async (args) => {
866
+ if (args.filePath) throw new Error("wxa-runner \u6682\u4E0D\u652F\u6301\u6307\u5B9A filePath\uFF0C\u4E0D\u4F20\u65F6\u4FDD\u5B58\u6210\u529F\u540E\u8FD4\u56DE savedFilePath");
867
+ const res = isTempFile(args.tempFilePath) ? await fetch(args.tempFilePath) : null;
868
+ if (!res?.ok) throw new Error(`tempFilePath file not exist ${args.tempFilePath}`);
869
+ const data = await res.blob();
870
+ const savedFilePath = await saveTempFile(data, tempFileType(args.tempFilePath, data.type, "tmp"));
871
+ await removeTempFile(args.tempFilePath);
872
+ storage.set(NAMESPACE, savedFilePath, { size: data.size, createTime: Math.floor(Date.now() / 1e3) });
873
+ return { savedFilePath };
874
+ },
875
+ getSavedFileList: async () => {
876
+ const fileList = [];
877
+ for (const filePath of storage.keys(NAMESPACE)) {
878
+ const saved = await liveRecord(filePath);
879
+ if (saved) fileList.push({ filePath, ...saved });
880
+ }
881
+ return { fileList };
882
+ },
883
+ getSavedFileInfo: async (args) => {
884
+ const saved = await liveRecord(args.filePath);
885
+ if (!saved) throw new Error(`file not exist ${args.filePath}`);
886
+ return { ...saved };
887
+ },
888
+ removeSavedFile: async (args) => {
889
+ if (!record(args.filePath)) throw new Error(`file not exist ${args.filePath}`);
890
+ storage.remove(NAMESPACE, args.filePath);
891
+ await removeTempFile(args.filePath);
892
+ }
893
+ };
894
+ }
895
+
834
896
  // src/runtime/host/api/storage.ts
835
897
  var LIMIT_SIZE_KB = 10 * 1024;
836
898
  var MiniProgramStorage = class {
@@ -1919,9 +1981,10 @@ html, body { margin: 0; height: 100%; background: radial-gradient(120% 80% at 50
1919
1981
  publish(kind, win, event, data, webviewIds) {
1920
1982
  if (kind === "service") {
1921
1983
  const targets = webviewIds?.length ? webviewIds.map((id) => this.pages.get(Number(id))) : this.livePages();
1984
+ const message = event === "canvasActionsChanged" ? withDrawableImages(data) : data;
1922
1985
  for (const page of targets) {
1923
1986
  page?.dispatch((bridge, target) => {
1924
- const payload2 = cloneInto(target, data);
1987
+ const payload2 = cloneInto(target, message);
1925
1988
  queueMicrotask(() => bridge.__dispatchSub(event, payload2, 0));
1926
1989
  });
1927
1990
  }
@@ -2169,6 +2232,7 @@ html, body { margin: 0; height: 100%; background: radial-gradient(120% 80% at 50
2169
2232
  reLaunch: routeApi((args) => this.reLaunch(args.url)),
2170
2233
  switchTab: routeApi((args) => this.switchTab(args.url)),
2171
2234
  ...createStorageApis(this.storage),
2235
+ ...createSavedFileApis(this.storage),
2172
2236
  ...createSystemApis({
2173
2237
  device: this.device,
2174
2238
  baseLibVersion: this.info.baseLibVersion,
@@ -2208,6 +2272,20 @@ html, body { margin: 0; height: 100%; background: radial-gradient(120% 80% at 50
2208
2272
  if (value === void 0) return value;
2209
2273
  return win.JSON.parse(JSON.stringify(value));
2210
2274
  }
2275
+ function withDrawableImages(message) {
2276
+ const actions = message?.data?.actions;
2277
+ if (!Array.isArray(actions)) return message;
2278
+ return {
2279
+ ...message,
2280
+ data: {
2281
+ ...message.data,
2282
+ actions: actions.map((action) => {
2283
+ const [src, ...rest] = action.data ?? [];
2284
+ return action.method === "drawImage" && typeof src === "string" ? { ...action, data: [tempFileRootPath(src), ...rest] } : action;
2285
+ })
2286
+ }
2287
+ };
2288
+ }
2211
2289
  function stripHtml(path) {
2212
2290
  return path.replace(/\.html$/, "");
2213
2291
  }