mirror of
https://github.com/anomalyco/opencode.git
synced 2026-11-08 09:37:47 +03:00
78 lines
3.3 KiB
TypeScript
78 lines
3.3 KiB
TypeScript
import { describe, expect, test } from "bun:test"
|
|
import path from "node:path"
|
|
import { Glob } from "bun"
|
|
|
|
// three.js must never reach the Worker's server bundle: its module start-up creates an
|
|
// AbortController, which Cloudflare Workers forbid during global start-up (every page answers 500).
|
|
// Modules that use it load only in the browser, through a loader the server build compiles away:
|
|
// const loadStage = import.meta.env.SSR ? undefined : () => import("./stage")
|
|
// script/check-server-bundle.ts checks the built output too.
|
|
|
|
const src = path.resolve(import.meta.dir, "..", "src")
|
|
// Forward slashes on every platform, so they compare with import specifiers.
|
|
const files = (await Array.fromAsync(new Glob("**/*.{ts,tsx}").scan({ cwd: src }))).map((file) =>
|
|
file.replaceAll("\\", "/"),
|
|
)
|
|
const sources = new Map(
|
|
await Promise.all(files.map(async (file) => [file, await Bun.file(path.join(src, file)).text()] as const)),
|
|
)
|
|
|
|
// Modules that import three.js as a value (statically or dynamically), and every module that imports
|
|
// one of those as a value: all of them are browser-only.
|
|
// One import statement: up to its `from`, never running on into the next statement.
|
|
const clause = String.raw`(?:^|\n)import\s+(?!type\b)(?:(?!\nimport\b)[^;])*?from\s+`
|
|
const usesThree = (text: string) => new RegExp(`${clause}"three[/"]|import\\("three[/"]`).test(text)
|
|
const browserOnly = (() => {
|
|
const found = new Set(files.filter((file) => usesThree(sources.get(file) ?? "")))
|
|
const grow = (): Set<string> => {
|
|
const next = files.filter(
|
|
(file) => !found.has(file) && [...found].some((target) => staticallyImports(file, target)),
|
|
)
|
|
if (!next.length) return found
|
|
next.forEach((file) => found.add(file))
|
|
return grow()
|
|
}
|
|
return grow()
|
|
})()
|
|
|
|
function specifiers(file: string, target: string) {
|
|
const relative = path.posix.relative(path.posix.dirname(file), target).replace(/\.tsx?$/, "")
|
|
return [relative.startsWith(".") ? relative : `./${relative}`, `~/${target.replace(/\.tsx?$/, "")}`]
|
|
}
|
|
|
|
function staticallyImports(file: string, target: string) {
|
|
const text = sources.get(file) ?? ""
|
|
return specifiers(file, target).some((specifier) => new RegExp(`${clause}"${escape(specifier)}"`).test(text))
|
|
}
|
|
|
|
function escape(value: string) {
|
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
|
|
}
|
|
|
|
describe("server bundle", () => {
|
|
test("three.js is used somewhere (the check is live)", () => {
|
|
expect(browserOnly.size).toBeGreaterThan(0)
|
|
})
|
|
|
|
test("browser-only three.js modules are imported only through an SSR-guarded loader", () => {
|
|
const unguarded = files.flatMap((file) =>
|
|
[...browserOnly]
|
|
.filter((target) => target !== file && !browserOnly.has(file))
|
|
.flatMap((target) =>
|
|
specifiers(file, target).flatMap((specifier) => {
|
|
const text = sources.get(file) ?? ""
|
|
const dynamic = new RegExp(`import\\("${escape(specifier)}"\\)`, "g")
|
|
const guarded = new RegExp(
|
|
`import\\.meta\\.env\\.SSR\\s*\\?\\s*undefined\\s*:\\s*\\(\\)\\s*=>\\s*import\\("${escape(specifier)}"\\)`,
|
|
"g",
|
|
)
|
|
const all = text.match(dynamic)?.length ?? 0
|
|
const safe = text.match(guarded)?.length ?? 0
|
|
return all > safe || staticallyImports(file, target) ? [`${file} → ${target}`] : []
|
|
}),
|
|
),
|
|
)
|
|
expect(unguarded).toEqual([])
|
|
})
|
|
})
|