diff --git a/scripts/build/runtime.ts b/scripts/build/runtime.ts deleted file mode 100755 index 83624b9c..00000000 --- a/scripts/build/runtime.ts +++ /dev/null @@ -1,212 +0,0 @@ -#!/usr/bin/env bun -import { config as dotenv } from "dotenv"; -import { existsSync } from "fs"; -import { rm } from "fs/promises"; -import path from "path"; -import { fileURLToPath } from "url"; -import { $ } from "bun"; -import { buildConfig } from "./helpers"; - -const __dirname = path.dirname(fileURLToPath(import.meta.url)); -const envPath = path.resolve(__dirname, "../../.env"); -dotenv({ path: envPath }); - -if (process.argv.includes("--help") || process.argv.includes("-h")) { - console.log(` -šŸ—ļø Runtime Build Script - -Usage: bun run scripts/build/runtime.ts [options] - -Builds pertento-runtime to src/apps/runtime/dist - -Options: - --help, -h Show this help message - --core Build only runtime-core (default) - --experiments Build only runtime-experiments - --tracking Build only runtime-tracking - --editor-setup Build only editor-setup - --all Build all runtimes - -Example: - bun run scripts/build/runtime.ts - bun run scripts/build/runtime.ts --experiments - bun run scripts/build/runtime.ts --tracking - bun run scripts/build/runtime.ts --editor-setup - bun run scripts/build/runtime.ts --all -`); - process.exit(0); -} - -const formatFileSize = (bytes: number): string => { - const units = ["B", "KB", "MB", "GB"]; - let size = bytes; - let unitIndex = 0; - - while (size >= 1024 && unitIndex < units.length - 1) { - size /= 1024; - unitIndex++; - } - - return `${size.toFixed(2)} ${units[unitIndex]}`; -}; - -interface RuntimeBuildConfig { - name: string; - srcDir: string; - configPath: string; - outFilename: string; - format: "iife" | "esm"; - entry?: string; -} - -const runtimeConfigs: Record = { - core: { - name: "runtime-core", - srcDir: "src/apps/runtime/runtime-core/src", - configPath: "src/apps/runtime/runtime-core/src/config.ts", - outFilename: "pertentoRuntime5.js", - format: "iife", - }, - experiments: { - name: "runtime-experiments", - srcDir: "src/apps/runtime/runtime-experiments/src", - configPath: "src/apps/runtime/runtime-experiments/src/config.ts", - outFilename: "pertentoRuntime5Experiments.js", - format: "esm", - }, - tracking: { - name: "runtime-tracking", - srcDir: "src/apps/runtime/runtime-tracking/src", - configPath: "src/apps/runtime/runtime-tracking/src/config.ts", - outFilename: "pertentoRuntime5Tracking.js", - format: "iife", - }, - editorSetup: { - name: "editor-setup", - srcDir: "src/apps/runtime/runtime-tracking/src", - configPath: "src/apps/runtime/runtime-tracking/src/config.ts", - outFilename: "pertentoEditorSetup.js", - format: "iife", - entry: "setup-for-editor-extension.ts", - }, -}; - -let buildTargets: string[] = ["core"]; -if (process.argv.includes("--experiments")) { - buildTargets = ["experiments"]; -} else if (process.argv.includes("--tracking")) { - buildTargets = ["tracking"]; -} else if (process.argv.includes("--editor-setup")) { - buildTargets = ["editorSetup"]; -} else if (process.argv.includes("--all")) { - buildTargets = ["core", "experiments", "tracking", "editorSetup"]; -} - -console.log("\nšŸš€ Starting runtime build process...\n"); - -const { BUILD_ENV } = process.env; -const isProduction = BUILD_ENV === "production"; -console.log( - `šŸ“‹ Build environment: ${BUILD_ENV || "development"} ${isProduction ? "(minified)" : "(development)"}\n`, -); - -const outdir = "src/apps/runtime/dist"; - -if (existsSync(outdir)) { - console.log(`šŸ—‘ļø Cleaning previous build at ${outdir}`); - await rm(outdir, { recursive: true, force: true }); -} - -const start = performance.now(); - -async function buildRuntime(target: string) { - const config = runtimeConfigs[target]; - if (!config) { - console.error(`Unknown runtime target: ${target}`); - process.exit(1); - } - - // Generate config with environment values - console.log(`šŸ”§ Generating config for ${config.name}...\n`); - buildConfig(config.configPath, target); - - console.log(`šŸ“¦ Building ${config.name}...\n`); - - const entryFile = config.entry || "index.ts"; - const result = await Bun.build({ - entrypoints: [`${config.srcDir}/${entryFile}`], - outdir, - minify: isProduction, - target: "browser", - format: config.format, - naming: config.outFilename, - define: { - "process.env.NODE_ENV": JSON.stringify( - isProduction ? "production" : "development", - ), - }, - }); - - if (!result.success) { - console.error(`Build failed for ${config.name}!`); - process.exit(1); - } - - // Compress the output files using Bun shell - const jsFile = path.join(outdir, config.outFilename); - if (existsSync(jsFile)) { - // Create gzip version - await $`gzip -k -f ${jsFile}`; - - // Create brotli version - await $`brotli -k -f ${jsFile}`; - } - - return result; -} - -// Build all targets -let allOutputs: { path: string; kind: string }[] = []; -for (const target of buildTargets) { - const result = await buildRuntime(target); - allOutputs = allOutputs.concat(result.outputs); -} - -const end = performance.now(); - -// Collect all compressed files -const allCompressed = buildTargets.flatMap((target) => { - const config = runtimeConfigs[target]; - if (!config) return []; - return [ - { path: path.join(outdir, `${config.outFilename}.gz`), kind: "compressed" }, - { path: path.join(outdir, `${config.outFilename}.br`), kind: "compressed" }, - ]; -}); - -const outputTable = allOutputs - .concat(allCompressed) - .filter((output) => existsSync(output.path)) - .map((output) => ({ - File: path.relative(process.cwd(), output.path), - Size: formatFileSize(Bun.file(output.path).size), - })); - -console.table(outputTable); -const buildTime = (end - start).toFixed(2); - -// Copy all output files to runtime-scripts directory -const runtimeScriptsDir = "runtime-scripts"; -if (!existsSync(runtimeScriptsDir)) { - await $`mkdir -p ${runtimeScriptsDir}`; -} - -console.log(`\nšŸ“ Copying files to ${runtimeScriptsDir}...`); -for (const output of allOutputs.concat(allCompressed)) { - if (existsSync(output.path)) { - const filename = path.basename(output.path); - await $`cp ${output.path} ${runtimeScriptsDir}/${filename}`; - } -} - -console.log(`\nāœ… Runtime build completed in ${buildTime}ms\n`); diff --git a/src/workspaces/officerdev/APP_CONVENTIONS.md b/src/workspaces/officerdev/APP_CONVENTIONS.md index b5182fb1..ea0b250e 100644 --- a/src/workspaces/officerdev/APP_CONVENTIONS.md +++ b/src/workspaces/officerdev/APP_CONVENTIONS.md @@ -97,9 +97,15 @@ Within a file, order sections as: 5. Types (props types for sub-components) 6. Helper components / functions -## No useCallback or useMemo (React 19) +## Prefer plain values over memoization -React 19's compiler handles memoization. Never use `useCallback` or `useMemo`. +Default to writing derivations plainly — most are cheap, and the memo costs more than the work. But +`useMemo`/`useCallback` are ordinary tools, not forbidden ones: reach for them when a value or callback +feeds a dependency array, when a computation is genuinely expensive, or when the prop goes to a memoized +child. See `CONVENTIONS.md` for the full rule and why the old "never, React 19 handles it" version was +wrong — the React Compiler is opt-in and is not installed here. + +The examples below are still the right default shape: ```tsx // Plain function — not wrapped in useCallback