Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 73 additions & 1 deletion test-package.js
Original file line number Diff line number Diff line change
@@ -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";

Expand Down Expand Up @@ -133,6 +133,13 @@ const CONTRACT = {
{ className: "UIController", tokens: ["registerButton(config: ToolbarButtonConfig): this;"] },
{ className: "Timeline", tokens: ["load(): Promise<void>;"] }
],
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"];
Expand Down Expand Up @@ -291,6 +298,69 @@ 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 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);
Expand Down Expand Up @@ -327,6 +397,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);

Expand Down
42 changes: 4 additions & 38 deletions vite.config.internal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -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
Expand Down
42 changes: 4 additions & 38 deletions vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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: {
Expand All @@ -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
Expand Down
61 changes: 61 additions & 0 deletions vite.shared.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { resolve } from "path";
import type { UserConfig } from "vite";

export const globals: Record<string, string> = {
"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<string, string> {
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<UserConfig> {
return {
define: {
"process.env.NODE_ENV": JSON.stringify(process.env["NODE_ENV"] || "development")
},
worker: {
format: "es" as const
},
resolve: {
alias: aliases(dirname)
}
};
}
Loading