From 25182d86e687d385e452442d73890c5e13e708fb Mon Sep 17 00:00:00 2001 From: cyfung1031 <44498510+cyfung1031@users.noreply.github.com> Date: Sun, 8 Feb 2026 00:29:55 +0900 Subject: [PATCH] =?UTF-8?q?[v1.3]=20=E5=88=86=E5=89=B2=E5=BC=8F=20ts.worke?= =?UTF-8?q?r.js?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/pack.js | 40 ++++++++++++++++-- src/pkg/utils/monaco-editor/index.ts | 13 ++++-- src/pkg/utils/monaco-editor/utils.ts | 62 ++++++++++++++++++++++++++++ 3 files changed, 108 insertions(+), 7 deletions(-) diff --git a/scripts/pack.js b/scripts/pack.js index 26d0f79a2..af0d6cd87 100644 --- a/scripts/pack.js +++ b/scripts/pack.js @@ -16,6 +16,31 @@ const PACK_FIREFOX = false; // ============================================================================ +// --- utils --- + +const MAX_CHUNK_SIZE = 3584 * 1024; // 3.5 MiB + +function addFileInChunks(zip, filePath, toDir, baseName, maxChunkSize = MAX_CHUNK_SIZE) { + const buffer = fs.readFileSync(filePath); + let offset = 0; + + const chunks = []; + while (offset < buffer.length) { + const end = Math.min(offset + maxChunkSize, buffer.length); + const chunk = buffer.subarray(offset, end); + chunks.push(chunk); + offset = end; + } + const len = chunks.length; + + for (let idx = 0; idx < len; idx += 1) { + const chunk = chunks[idx]; + // e.g. src/ts.worker.js.part0, src/ts.worker.js.part1, ... + const chunkPath = `${toDir}${baseName}.part${idx}`; + zip.file(chunkPath, chunk); + } +} + const createJSZip = () => { const currDate = new Date(); const dateWithOffset = new Date(currDate.getTime() - currDate.getTimezoneOffset() * 60000); @@ -24,6 +49,8 @@ const createJSZip = () => { return new JSZip(); }; +// --- utils --- + // 判断是否为beta版本 const version = semver.parse(packageInfo.version); if (version.prerelease.length) { @@ -80,7 +107,8 @@ delete chromeManifest.background.scripts; delete firefoxManifest.background.service_worker; delete firefoxManifest.sandbox; -// firefoxManifest.content_security_policy = "script-src 'self' blob:; object-src 'self' blob:"; +// firefoxManifest.content_security_policy 是为了支持动态组合的 ts.worker.js Blob URL +firefoxManifest.content_security_policy = "script-src 'self' blob:; object-src 'self' blob:"; firefoxManifest.browser_specific_settings = { gecko: { id: `{${ @@ -128,8 +156,14 @@ await Promise.all([ addDir(chrome, "./dist/ext", "", ["manifest.json"]), addDir(firefox, "./dist/ext", "", ["manifest.json", "ts.worker.js"]), ]); -// 添加ts.worker.js名字为gz -firefox.file("src/ts.worker.js.gz", await fs.readFile("./dist/ext/src/ts.worker.js", { encoding: "utf8" })); + +// Now split ts.worker.js into chunks (<4MB each) for Firefox +addFileInChunks( + firefox, + "./dist/ext/src/ts.worker.js", // source file on disk + "src/", // folder path inside zip + "ts.worker.js" // base name for chunked file +); // 导出zip包 chrome diff --git a/src/pkg/utils/monaco-editor/index.ts b/src/pkg/utils/monaco-editor/index.ts index 6c91de174..6a948b38e 100644 --- a/src/pkg/utils/monaco-editor/index.ts +++ b/src/pkg/utils/monaco-editor/index.ts @@ -1,10 +1,13 @@ import { globalCache, systemConfig } from "@App/pages/store/global"; import EventEmitter from "eventemitter3"; import { languages } from "monaco-editor"; -import { findGlobalInsertionInfo, updateGlobalCommentLine } from "./utils"; +import { findGlobalInsertionInfo, getTsWorkerPromise, updateGlobalCommentLine } from "./utils"; // 注册eslint const linterWorker = new Worker("/src/linter.worker.js"); +const editorWorker = new Worker("/src/editor.worker.js", { type: "module" }); +const tsWorkerPromise = getTsWorkerPromise(); + const langPromise = systemConfig.getLanguage(); const langs = { @@ -456,11 +459,13 @@ type LangEntry = (typeof langs)["zh-CN"]; export default function registerEditor() { window.MonacoEnvironment = { - getWorkerUrl(moduleId: any, label: any) { + // https://microsoft.github.io/monaco-editor/typedoc/interfaces/Environment.html#getWorker + // Returns Worker | Promise + getWorker(workerId: string, label: string) { if (label === "typescript" || label === "javascript") { - return "/src/ts.worker.js"; + return tsWorkerPromise; } - return "/src/editor.worker.js"; + return editorWorker; }, }; function asLangEntry(key: T) { diff --git a/src/pkg/utils/monaco-editor/utils.ts b/src/pkg/utils/monaco-editor/utils.ts index 978742ffb..dc6892904 100644 --- a/src/pkg/utils/monaco-editor/utils.ts +++ b/src/pkg/utils/monaco-editor/utils.ts @@ -1,5 +1,67 @@ import type { editor } from "monaco-editor"; +const getPartialBlob = (idx: number): Promise => + fetch(chrome.runtime.getURL(`/src/ts.worker.js.part${idx}`)) + .then((resp) => (resp.ok ? resp.blob() : null)) + .catch(() => null); +const combineBlobsToUrl = async (blobs: Blob[], defaultType?: string): Promise => { + const arrayBuffers: ArrayBuffer[] = []; + let totalLength = 0; + + // Read all blobs into ArrayBuffers and compute total length + for (const blob of blobs) { + const arrayBuffer = await blob.arrayBuffer(); + arrayBuffers.push(arrayBuffer); + totalLength += arrayBuffer.byteLength; // <-- sum, don't overwrite + } + + // Allocate a single Uint8Array large enough for everything + const combined = new Uint8Array(totalLength); + + // Copy each buffer into the combined array + let offset = 0; + for (const buffer of arrayBuffers) { + combined.set(new Uint8Array(buffer), offset); + offset += buffer.byteLength; + } + + // Create a single Blob out of the combined data + const type = defaultType || blobs[0]?.type || "application/octet-stream"; + const combinedBlob = new Blob([combined], { type }); + + // Create a Blob URL + const blobUrl = URL.createObjectURL(combinedBlob); + // 注意:此处生成的 Blob URL 在整个应用生命周期内用于 Worker,不会被释放。 + // 如果未来 Worker 支持销毁重建,请在销毁时调用 URL.revokeObjectURL(blobUrl) 释放资源。 + return blobUrl; +}; +export const getTsWorkerPromise = () => + fetch(chrome.runtime.getURL("/src/ts.worker.js.part0")) + .then((resp) => { + return resp.ok ? resp.blob() : null; + }) + .catch(() => { + return null; + }) + .then(async (blob) => { + let worker: Worker; + if (blob) { + // 有分割 + const blobs: Blob[] = []; + let idx = 0; + do { + blobs.push(blob); + blob = await getPartialBlob(++idx); + } while (blob); + const url = await combineBlobsToUrl(blobs, "text/javascript"); + worker = new Worker(url, { type: "module" }); + } else { + // 沒分割 + worker = new Worker("/src/ts.worker.js", { type: "module" }); + } + return worker; + }); + export const findGlobalInsertionInfo = (model: editor.ITextModel) => { const lineCount = model.getLineCount();