diff --git a/README.md b/README.md index 4d2b0b49f6..2f3e5d190c 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # 无名杀 +github仓库: + ## 快速启动 ### 环境要求 @@ -8,7 +10,7 @@ - [Node.js](https://nodejs.org/) ^20.19.0 || >=22.12.0 - [pnpm](https://pnpm.io/) >=8 -- Webview >=91 +- Webview: Chromium >= 91 || Safari >=16.4.0 (暂不支持Firefox) ### 安装依赖 diff --git a/index.html b/index.html index 753a0b77ed..25572685e2 100755 --- a/index.html +++ b/index.html @@ -335,7 +335,6 @@ }; } - diff --git a/jit/vite-plugin-jit.ts b/jit/vite-plugin-jit.ts index 0971b92555..57ad4630d0 100644 --- a/jit/vite-plugin-jit.ts +++ b/jit/vite-plugin-jit.ts @@ -1,6 +1,5 @@ import { normalizePath, Plugin } from "vite"; import { resolve } from "path"; -import { URL } from "url"; import fs from "fs"; import path from "path"; @@ -17,37 +16,6 @@ export default function vitePluginJIT(importMap: Record = {}): P root = config.root; }, - // 开发环境:虚拟 /sw.js - // 有vite自动编译,不启用 - // configureServer(server) { - // if (isBuild) return; - // server.middlewares.use(async (req, res, next) => { - // if (req.url === "/service-worker.js") { - // try { - // // 编译 - // const result = await server.transformRequest(new URL("./service-worker.ts", import.meta.url).pathname); - // if (result) { - // let code = result.code; - // res.setHeader("Content-Type", "application/javascript"); - // // sourcemap - // if (result.map) { - // const map = typeof result.map === "string" ? result.map : JSON.stringify(result.map); - // code += `\n//# sourceMappingURL=data:application/json;base64,${Buffer.from(map).toString("base64")}`; - // } - // res.end(code); - // return; - // } - // } catch (err) { - // res.statusCode = 500; - // res.end("JIT Service Worker load error: " + err); - // return; - // } - // } - // next(); - // }); - // }, - - // 生产环境:单独打包 async buildStart() { if (!isBuild) return; const swEntry = resolve(import.meta.dirname, "./service-worker.ts"); @@ -56,10 +24,12 @@ export default function vitePluginJIT(importMap: Record = {}): P id: swEntry, fileName: "service-worker.js", }); - for (const i in importMap) { - const resolved = await this.resolve(importMap[i], undefined, { skipSelf: true }); + for (const key in importMap) { + const resolved = await this.resolve(importMap[key], undefined, { skipSelf: true }); if (resolved?.id) { - resolvedImportMap[i] = normalizePath("/" + path.relative(root, resolved.id)); + resolvedImportMap[key] = normalizePath("/" + path.relative(root, resolved.id)); + } else { + resolvedImportMap[key] = importMap[key]; } } }, @@ -69,9 +39,15 @@ export default function vitePluginJIT(importMap: Record = {}): P fs.mkdirSync(path.dirname(output), { recursive: true }); fs.writeFileSync(output, "export default " + JSON.stringify(resolvedImportMap, null, 2)); this.warn(`[vite:jit-importmap] Wrote ${output}`); - + fs.mkdirSync(path.dirname(path.resolve("dist/jit/test/canUse.ts")), { recursive: true }); - fs.copyFileSync(path.resolve("jit/test/canUse.ts"),path.resolve("dist/jit/test/canUse.ts")) + fs.copyFileSync(path.resolve("jit/test/canUse.ts"), path.resolve("dist/jit/test/canUse.ts")); + }, + + transformIndexHtml(html) { + if (!isBuild) return; + const script = ``; + return html.replace("", `${script}\n`); }, }; } diff --git a/noname/library/element/content.js b/noname/library/element/content.js index d566f6eb89..e656e623ad 100644 --- a/noname/library/element/content.js +++ b/noname/library/element/content.js @@ -6825,10 +6825,6 @@ player.removeVirtualEquip(card); .filter((event, player) => { return event.name == "phase" || [player, target].includes(event.player); }) - .vars({ - cardsx: cards, - evt: event, - }) .step(async (event, trigger, player) => { if (cards?.some(card => get.position(card) == "s")) { evt.isDestoryed = true; diff --git a/noname/library/element/player.js b/noname/library/element/player.js index 72e6db2792..46e23d7482 100644 --- a/noname/library/element/player.js +++ b/noname/library/element/player.js @@ -542,10 +542,7 @@ export class Player extends HTMLDivElement { firstDo: true, priority: Infinity, }) - .vars({ - index, - }) - .then(() => { + .step(async (event, trigger, player) => { delete player.storage[`temp_tip_${index}`]; player.removeTip(index); }) diff --git a/noname/util/error.ts b/noname/util/error.ts index baf4ba96c0..4b2c5046f4 100644 --- a/noname/util/error.ts +++ b/noname/util/error.ts @@ -1,5 +1,7 @@ import { promiseErrorHandlerMap } from "@/util/promise-error-handler"; import StackTrace from "stacktrace-js"; +import ErrorStackParser from "error-stack-parser"; +import StackTraceGPS from "stacktrace-gps"; class CodeSnippet { static #snippetStack: CodeSnippet[] = []; @@ -352,6 +354,27 @@ class ErrorManager { } } + /** + * 目前和StackTrace.fromError一样 + * @param error + * @param opts + * @returns + */ + static async fromError(error: Error, opts?: StackTraceGPS.Options): Promise<{ origin: StackFrame; source?: StackFrame }[]> { + const gps = new StackTraceGPS(opts); + const stackframes = ErrorStackParser.parse(error); + return Promise.all( + stackframes.map(async sf => { + try { + const source = await gps.pinpoint(sf); + return { origin: sf, source }; + } catch { + return { origin: sf }; + } + }) + ); + } + /** * ```plain * 设置错误报告器 @@ -402,8 +425,8 @@ export async function setOnError({ lib, game, get, _status }) { window.onerror = async function (msg: string | Event, src?: string, line?: number, column?: number, err?: Error) { if (!err) return; - const stackframes = await StackTrace.fromError(err); - const frame = stackframes[0]; + const stackframes = await ErrorManager.fromError(err); + const frame = stackframes[0].source || stackframes[0].origin; const log: string[] = []; const winPath = window.__dirname ? "file:///" + (__dirname.replace(new RegExp("\\\\", "g"), "/") + "/") : ""; log.push(`错误文件: ${typeof src == "string" ? decodeURI(src).replace(lib.assetURL, "").replace(winPath, "") : "未知文件"}`); @@ -442,7 +465,7 @@ export async function setOnError({ lib, game, get, _status }) { return showCode; }; // 解析step content的错误 - if (stackframes[0].functionName === "packStep") { + if (frame.functionName === "packStep") { const codes = _status.event.content.originals[_status.event.step]; if (typeof codes == "function") { const regex = /:(\d+):\d+/; @@ -457,7 +480,8 @@ export async function setOnError({ lib, game, get, _status }) { const sourcePath = "local:" + decodeURI(src).replace(lib.assetURL, "").replace(winPath, ""); //获取sourcemap try { - if (!frame.fileName) throw new Error(); + const source = stackframes[0].source; + if (!source?.fileName) throw new Error(); let rawSourceMap = lib.init.reqSync(sourcePath + ".map"); if (!rawSourceMap) throw new Error(); @@ -491,12 +515,11 @@ export async function setOnError({ lib, game, get, _status }) { } } - const file = relativeUrl(src, frame.fileName); + const file = relativeUrl(src, source.fileName); const content = sourceMap.sourcesContent[sourceMap.sources.indexOf(file)]; log.push(...createShowCode(content, frame.lineNumber || 0)); } catch (e) { - console.log(e); let code = lib.init.reqSync(sourcePath); if (code) log.push(...createShowCode(code, frame.lineNumber || 0)); } @@ -505,7 +528,8 @@ export async function setOnError({ lib, game, get, _status }) { if (err && err.stack) { log.push(`${err.name}: ${err.message}`); log.push( - ...stackframes.map(f => { + ...stackframes.map(frame => { + const f = frame.source || frame.origin; return ` at ${f.functionName || "(anonymous)"} (${decodeURI(f.fileName || "") .replace(new RegExp(lib.assetURL, "g"), "") .replace(new RegExp(winPath, "g"), "")}:${f.lineNumber}:${f.columnNumber})`; diff --git a/package.json b/package.json index 2fe584fbf7..1b8f7d1aa0 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "core-js-bundle": "^3.43.0", "crypto-js": "^4.2.0", "dedent": "^1.6.0", + "error-stack-parser": "^2.1.4", "eslint-linter-browserify": "^9.36.0", "express": "^5.1.0", "jszip": "^2.6.1", @@ -48,6 +49,7 @@ "path-browserify": "^1.0.1", "pinyin-pro": "^3.27.0", "pressure": "^2.2.0", + "stacktrace-gps": "^3.1.2", "stacktrace-js": "^2.0.2", "typescript": "^5.9.2", "vue": "^3.5.17", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 55147ff2b2..57b9d1a96f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -74,6 +74,9 @@ importers: dedent: specifier: ^1.6.0 version: 1.7.0 + error-stack-parser: + specifier: ^2.1.4 + version: 2.1.4 eslint-linter-browserify: specifier: ^9.36.0 version: 9.36.0 @@ -98,6 +101,9 @@ importers: pressure: specifier: ^2.2.0 version: 2.2.0 + stacktrace-gps: + specifier: ^3.1.2 + version: 3.1.2 stacktrace-js: specifier: ^2.0.2 version: 2.0.2 diff --git a/scripts/build.ts b/scripts/build.ts index 12ce7717de..31df198ec9 100644 --- a/scripts/build.ts +++ b/scripts/build.ts @@ -76,7 +76,50 @@ else { // 继承vite.config.ts await build({ - plugins: [viteStaticCopy({ targets: staticModules })], + build: { + // 需要覆写map文件,必须外置 + sourcemap: argv.sourcemap || false, + }, + plugins: [ + viteStaticCopy({ targets: staticModules }), + (() => { + let hasSourceMap = false; + return { + name: "rewrite-sourcemap-path", + enforce: "post", + apply: "build", + + configResolved(config) { + hasSourceMap = !!config.build.sourcemap; + }, + /** + * 重写sourcemap的sources路径 + * 将指向根目录变为指向dist/目录,以适配外部平台 + * @example + * 打包结果:dist/a/bundle.js 指向 a/b/c.ts + * 转换:../../a/b/c.ts -> b/c.ts + */ + writeBundle(_, bundle) { + if (!hasSourceMap) return; + for (const [fileName, chunk] of Object.entries(bundle)) { + if (!fileName.endsWith(".map") || chunk.type !== "asset") continue; + + try { + const mapPath = path.resolve("dist", fileName); + const jsDir = path.dirname(fileName.replace(/\.map$/, "")); + const map = JSON.parse(chunk.source as string); + + map.sources = map.sources.map((src: string) => path.relative(jsDir, src.replace(/^(\.\.\/)+/, ""))); + + fs.writeFileSync(mapPath, JSON.stringify(map)); + } catch (err) { + console.warn(`rewrite-sourcemap-path: failed for ${fileName}`, err); + } + } + }, + }; + })(), + ], }); await esbuild({ diff --git a/vite.config.ts b/vite.config.ts index 821a8248a7..64cc27ee53 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,6 +1,4 @@ import { defineConfig } from "vite"; -import path from "path"; -import fs from "fs"; import vue from "@vitejs/plugin-vue"; import jit from "./jit/vite-plugin-jit"; @@ -14,18 +12,17 @@ export default defineConfig({ resolve: { alias: { "@": "/noname", - "@noname": "/noname.js" + "@noname": "/noname.js", }, - extensions: [".tsx", ".ts", ".js"], + extensions: [".tsx", ".ts", ".js", ".vue"], }, build: { - // 需要覆写map文件,必须外置,请勿更改 - sourcemap: true, + minify: false, rollupOptions: { preserveEntrySignatures: "strict", treeshake: false, input: { - main: "index.html", + index: "index.html", }, output: { preserveModules: true, // 保留文件结构 @@ -45,37 +42,9 @@ export default defineConfig({ plugins: [ vue(), jit({ - "vue": "vue/dist/vue.esm-browser.js", - "@": "/noname", + vue: "vue/dist/vue.esm-browser.js", "@noname": "/noname.js", }), - { - name: "rewrite-sourcemap-path", - enforce: "post", - apply: "build", - /** - * 重写sourcemap的sources路径 - * 将指向根目录变为指向dist/目录,以适配外部平台 - * @example - * 打包结果:dist/a/bundle.js 指向 a/b/c.ts - * 转换:../../a/b/c.ts -> b/c.ts - */ - writeBundle(_, bundle) { - for (const [fileName, chunk] of Object.entries(bundle)) { - if (chunk.type !== "chunk") continue; - - const mapPath = path.resolve("dist", `${fileName}.map`); - if (!fs.existsSync(mapPath)) continue; - - const map = JSON.parse(fs.readFileSync(mapPath, "utf-8")); - const bundleDir = path.dirname(fileName); - - map.sources = map.sources.map((src: string) => path.relative(bundleDir, src.replace(/^(\.\.\/)+/, ""))); - - fs.writeFileSync(mapPath, JSON.stringify(map)); - } - }, - }, ], server: { open: true,