From c45d3fe7845f45ec84d804c2659a8bcf907a8c9f Mon Sep 17 00:00:00 2001 From: Kratos2k7 Date: Wed, 25 Mar 2026 19:14:36 +0500 Subject: [PATCH 1/2] =?UTF-8?q?fix:=20IIFE=20crash=20fixed=20=E2=80=94=208?= =?UTF-8?q?=20pixi.js=20submodule=20globals=20now=20mapped=20to=20PIXI.=20?= =?UTF-8?q?pixi-filters=20externalized=20=E2=80=94=20no=20longer=20bundled?= =?UTF-8?q?=20with=20mixed=20boundary;=20mapped=20to=20PIXI=20global.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test-package.js | 81 ++++++++++++++++++++++++++++++++++++++++- vite.config.internal.ts | 42 ++------------------- vite.config.ts | 42 ++------------------- vite.shared.ts | 61 +++++++++++++++++++++++++++++++ 4 files changed, 149 insertions(+), 77 deletions(-) create mode 100644 vite.shared.ts diff --git a/test-package.js b/test-package.js index fb36e34..9dddcaa 100644 --- a/test-package.js +++ b/test-package.js @@ -1,4 +1,4 @@ -import { existsSync, readFileSync, readdirSync } from "fs"; +import { existsSync, readFileSync, readdirSync, statSync } from "fs"; import { resolve, dirname } from "path"; import { fileURLToPath } from "url"; @@ -133,6 +133,13 @@ const CONTRACT = { { className: "UIController", tokens: ["registerButton(config: ToolbarButtonConfig): this;"] }, { className: "Timeline", tokens: ["load(): Promise;"] } ], + bundleSizeLimits: { + "dist/shotstack-studio.umd.js": 3 * 1024 * 1024, + "dist/shotstack-studio.es.js": 4 * 1024 * 1024, + "dist/internal.umd.js": 2.5 * 1024 * 1024, + "dist/internal.es.js": 3 * 1024 * 1024 + }, + umdBundles: ["dist/shotstack-studio.umd.js", "dist/internal.umd.js"] }; const BROWSER_GLOBALS = ["self", "window", "document", "navigator", "HTMLCanvasElement"]; @@ -291,6 +298,76 @@ const checkPackageExports = () => { printResult("package.json exports contract", true); }; +const checkUmdGlobalMappings = () => { + const errors = []; + + for (const bundlePath of CONTRACT.umdBundles) { + const fullPath = resolve(__dirname, bundlePath); + const fd = readFileSync(fullPath, "utf-8").slice(0, 2048); + + const requirePattern = /require\("([^"]+)"\)/g; + const requiredModules = []; + let match; + while ((match = requirePattern.exec(fd)) !== null) { + requiredModules.push(match[1]); + } + + const iifeMatch = fd.match(/\((\w+)=typeof globalThis[^)]+,(\w+)\(([^)]+)\)\)/); + if (!iifeMatch) { + errors.push(`${bundlePath}: Could not parse IIFE global branch from UMD wrapper.`); + continue; + } + + const iifeArgs = iifeMatch[3]; + + const iifeArgCount = iifeArgs.split(",").length; + + const factoryMatch = fd.match(/\}\)\([^,]+,function\(([^)]*)\)/); + if (!factoryMatch) { + errors.push(`${bundlePath}: Could not parse factory function signature.`); + continue; + } + const factoryParamCount = factoryMatch[1].split(",").length; + + if (iifeArgCount !== factoryParamCount) { + errors.push( + `${bundlePath}: IIFE branch passes ${iifeArgCount} args but factory expects ${factoryParamCount} params. ` + + `Mismatch means some dependencies will be undefined at runtime. ` + + `Check that every non-side-effect external has a global mapping in vite.shared.ts.` + ); + } + } + + if (errors.length > 0) { + failWithDetails("UMD global mappings", errors); + } + printResult("UMD global mappings", true); +}; + +const checkBundleSizes = () => { + const errors = []; + const details = []; + + for (const [file, maxBytes] of Object.entries(CONTRACT.bundleSizeLimits)) { + const fullPath = resolve(__dirname, file); + if (!existsSync(fullPath)) continue; + + const actualBytes = statSync(fullPath).size; + const actualMB = (actualBytes / (1024 * 1024)).toFixed(2); + const maxMB = (maxBytes / (1024 * 1024)).toFixed(1); + details.push(`${file}: ${actualMB} MB (limit: ${maxMB} MB)`); + + if (actualBytes > maxBytes) { + errors.push(`${file} is ${actualMB} MB, exceeds limit of ${maxMB} MB`); + } + } + + if (errors.length > 0) { + failWithDetails("Bundle size thresholds", errors); + } + printResult("Bundle size thresholds", true, details); +}; + const runRuntimeExportSmokeTest = async (name, modulePath, expectedExports) => { try { const module = await import(modulePath); @@ -327,6 +404,8 @@ checkDeclarationSurface(); checkInternalDeclarationSurface(); checkNoChunkArtifactsOrImports(); checkPackageExports(); +checkUmdGlobalMappings(); +checkBundleSizes(); await runRuntimeExportSmokeTest("Runtime export smoke test", "./dist/shotstack-studio.es.js", CONTRACT.runtimeExports); await runRuntimeExportSmokeTest("Internal runtime export smoke test", "./dist/internal.es.js", CONTRACT.internalRuntimeExports); diff --git a/vite.config.internal.ts b/vite.config.internal.ts index db53c16..461f320 100644 --- a/vite.config.internal.ts +++ b/vite.config.internal.ts @@ -2,24 +2,13 @@ import { defineConfig } from "vite"; import { resolve } from "path"; import dts from "vite-plugin-dts"; +import { globals, external, sharedConfig } from "./vite.shared"; -const globals = { - "pixi.js": "PIXI", - howler: "Howler", - "opentype.js": "opentype", - "@ffmpeg/ffmpeg": "FFmpeg", - harfbuzzjs: "createHarfBuzz", - "@napi-rs/canvas": "Canvas" -}; +const shared = sharedConfig(__dirname); const INTERNAL_TYPES_ENTRY_STUB = "export * from './internal'"; export default defineConfig({ - define: { - "process.env.NODE_ENV": JSON.stringify(process.env["NODE_ENV"] || "development") - }, - worker: { - format: "es" - }, + ...shared, plugins: [ dts({ rollupTypes: true, @@ -43,25 +32,6 @@ export default defineConfig({ } }) ], - resolve: { - alias: { - "@core": resolve(__dirname, "src/core"), - "@canvas": resolve(__dirname, "src/components/canvas"), - "@timeline": resolve(__dirname, "src/components/timeline"), - "@shared": resolve(__dirname, "src/core/shared"), - "@schemas": resolve(__dirname, "src/core/schemas"), - "@timing": resolve(__dirname, "src/core/timing"), - "@layouts": resolve(__dirname, "src/core/layouts"), - "@animations": resolve(__dirname, "src/core/animations"), - "@events": resolve(__dirname, "src/core/events"), - "@inputs": resolve(__dirname, "src/core/inputs"), - "@loaders": resolve(__dirname, "src/core/loaders"), - "@export": resolve(__dirname, "src/core/export"), - "@styles": resolve(__dirname, "src/styles"), - "@templates": resolve(__dirname, "src/templates"), - "@shotstack/shotstack-canvas": resolve(__dirname, "node_modules/@shotstack/shotstack-canvas/dist/entry.web.js") - } - }, build: { target: "esnext", emptyOutDir: false, @@ -72,11 +42,7 @@ export default defineConfig({ formats: ["es", "umd"] }, rollupOptions: { - external: id => { - if (id === "pixi.js" || id.startsWith("pixi.js/")) return true; - if (id.startsWith("@napi-rs/")) return true; - return ["harfbuzzjs", "opentype.js", "howler", "canvas"].includes(id); - }, + external, output: { globals, inlineDynamicImports: true diff --git a/vite.config.ts b/vite.config.ts index 1884ce5..8dd9529 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -2,23 +2,12 @@ import { defineConfig } from "vite"; import { resolve } from "path"; import dts from "vite-plugin-dts"; +import { globals, external, sharedConfig } from "./vite.shared"; -const globals = { - "pixi.js": "PIXI", - howler: "Howler", - "opentype.js": "opentype", - "@ffmpeg/ffmpeg": "FFmpeg", - harfbuzzjs: "createHarfBuzz", - "@napi-rs/canvas": "Canvas" -}; +const shared = sharedConfig(__dirname); export default defineConfig({ - define: { - "process.env.NODE_ENV": JSON.stringify(process.env["NODE_ENV"] || "development") - }, - worker: { - format: "es" - }, + ...shared, plugins: [ dts({ rollupTypes: true, @@ -27,25 +16,6 @@ export default defineConfig({ pathsToAliases: true }) ], - resolve: { - alias: { - "@core": resolve(__dirname, "src/core"), - "@canvas": resolve(__dirname, "src/components/canvas"), - "@timeline": resolve(__dirname, "src/components/timeline"), - "@shared": resolve(__dirname, "src/core/shared"), - "@schemas": resolve(__dirname, "src/core/schemas"), - "@timing": resolve(__dirname, "src/core/timing"), - "@layouts": resolve(__dirname, "src/core/layouts"), - "@animations": resolve(__dirname, "src/core/animations"), - "@events": resolve(__dirname, "src/core/events"), - "@inputs": resolve(__dirname, "src/core/inputs"), - "@loaders": resolve(__dirname, "src/core/loaders"), - "@export": resolve(__dirname, "src/core/export"), - "@styles": resolve(__dirname, "src/styles"), - "@templates": resolve(__dirname, "src/templates"), - "@shotstack/shotstack-canvas": resolve(__dirname, "node_modules/@shotstack/shotstack-canvas/dist/entry.web.js") - } - }, build: { target: "esnext", lib: { @@ -55,11 +25,7 @@ export default defineConfig({ formats: ["es", "umd"] }, rollupOptions: { - external: id => { - if (id === "pixi.js" || id.startsWith("pixi.js/")) return true; - if (id.startsWith("@napi-rs/")) return true; - return ["harfbuzzjs", "opentype.js", "howler", "canvas"].includes(id); - }, + external, output: { globals, inlineDynamicImports: true diff --git a/vite.shared.ts b/vite.shared.ts new file mode 100644 index 0000000..47fcf33 --- /dev/null +++ b/vite.shared.ts @@ -0,0 +1,61 @@ +import { resolve } from "path"; +import type { UserConfig } from "vite"; + +export const globals: Record = { + "pixi.js": "PIXI", + "pixi.js/app": "PIXI", + "pixi.js/events": "PIXI", + "pixi.js/graphics": "PIXI", + "pixi.js/text": "PIXI", + "pixi.js/text-html": "PIXI", + "pixi.js/sprite-tiling": "PIXI", + "pixi.js/filters": "PIXI", + "pixi.js/mesh": "PIXI", + "pixi-filters": "PIXI", + howler: "Howler", + "opentype.js": "opentype", + "@ffmpeg/ffmpeg": "FFmpeg", + harfbuzzjs: "createHarfBuzz", + "@napi-rs/canvas": "Canvas" +}; + +export function external(id: string): boolean { + if (id === "pixi.js" || id.startsWith("pixi.js/")) return true; + if (id === "pixi-filters" || id.startsWith("pixi-filters/")) return true; + if (id.startsWith("@napi-rs/")) return true; + return ["harfbuzzjs", "opentype.js", "howler", "canvas"].includes(id); +} + +export function aliases(dirname: string): Record { + return { + "@core": resolve(dirname, "src/core"), + "@canvas": resolve(dirname, "src/components/canvas"), + "@timeline": resolve(dirname, "src/components/timeline"), + "@shared": resolve(dirname, "src/core/shared"), + "@schemas": resolve(dirname, "src/core/schemas"), + "@timing": resolve(dirname, "src/core/timing"), + "@layouts": resolve(dirname, "src/core/layouts"), + "@animations": resolve(dirname, "src/core/animations"), + "@events": resolve(dirname, "src/core/events"), + "@inputs": resolve(dirname, "src/core/inputs"), + "@loaders": resolve(dirname, "src/core/loaders"), + "@export": resolve(dirname, "src/core/export"), + "@styles": resolve(dirname, "src/styles"), + "@templates": resolve(dirname, "src/templates"), + "@shotstack/shotstack-canvas": resolve(dirname, "node_modules/@shotstack/shotstack-canvas/dist/entry.web.js") + }; +} + +export function sharedConfig(dirname: string): Partial { + return { + define: { + "process.env.NODE_ENV": JSON.stringify(process.env["NODE_ENV"] || "development") + }, + worker: { + format: "es" as const + }, + resolve: { + alias: aliases(dirname) + } + }; +} From c41d4c1bcab847dfde38a590083f6b1b78fc75fb Mon Sep 17 00:00:00 2001 From: dazzatronus Date: Thu, 26 Mar 2026 10:17:43 +1100 Subject: [PATCH 2/2] refactor: remove unused require pattern extraction --- test-package.js | 7 ------- 1 file changed, 7 deletions(-) diff --git a/test-package.js b/test-package.js index 9dddcaa..ff08fe5 100644 --- a/test-package.js +++ b/test-package.js @@ -305,13 +305,6 @@ const checkUmdGlobalMappings = () => { const fullPath = resolve(__dirname, bundlePath); const fd = readFileSync(fullPath, "utf-8").slice(0, 2048); - const requirePattern = /require\("([^"]+)"\)/g; - const requiredModules = []; - let match; - while ((match = requirePattern.exec(fd)) !== null) { - requiredModules.push(match[1]); - } - const iifeMatch = fd.match(/\((\w+)=typeof globalThis[^)]+,(\w+)\(([^)]+)\)\)/); if (!iifeMatch) { errors.push(`${bundlePath}: Could not parse IIFE global branch from UMD wrapper.`);