1import * as babel from "@babel/core"
2import { assert, describe, it } from "@effect/vitest"
3import * as esbuild from "esbuild"
4import * as fs from "node:fs/promises"
5import * as path from "node:path"
6import { fileURLToPath } from "node:url"
7
8const packageDir = fileURLToPath(new URL("..", import.meta.url))
9const effectSrcDir = fileURLToPath(new URL("../../../effect/src/", import.meta.url))
10
11// The published build runs babel with annotate-pure-calls over the compiler
12// output. Bundlers rely on those annotations to drop unused module-scope
13// calls, so the same step is reproduced here for every effect source module.
14// Without it esbuild shakes source differently from the published package.
15const annotatePureCalls: esbuild.Plugin = {
16 name: "annotate-pure-calls",
17 setup(build) {
18 build.onLoad({ filter: /\.ts$/ }, async (args) => {
19 if (!args.path.startsWith(effectSrcDir)) {
20 return undefined
21 }
22 const source = await fs.readFile(args.path, "utf8")
23 const transpiled = await esbuild.transform(source, {
24 loader: "ts",
25 format: "esm",
26 target: "es2022",
27 sourcefile: args.path,
28 tsconfigRaw: { compilerOptions: { verbatimModuleSyntax: true } }
29 })
30 const annotated = await babel.transformAsync(transpiled.code, {
31 babelrc: false,
32 configFile: false,
33 compact: false,
34 cwd: packageDir,
35 filename: args.path,
36 plugins: ["annotate-pure-calls"]
37 })
38 return { contents: annotated!.code!, loader: "js" }
39 })
40 }
41}
42
43// Bundles a fixture the way wrangler does (esbuild, worker conditions) and
44// returns the effect source modules that made it into the output. Only the
45// output entry of the metafile lists retained modules; `metafile.inputs` is
46// the whole import graph, tree-shaken or not.
47const bundledEffectModules = async (fixture: string): Promise<ReadonlyArray<string>> => {
48 const result = await esbuild.build({
49 entryPoints: [path.join(packageDir, "fixtures", fixture)],
50 bundle: true,
51 write: false,
52 metafile: true,
53 format: "esm",
54 platform: "browser",
55 target: "es2022",
56 conditions: ["workerd", "worker", "browser"],
57 logLevel: "silent",
58 absWorkingDir: packageDir,
59 plugins: [annotatePureCalls]
60 })
61 const [output] = Object.values(result.metafile.outputs)
62 return Object.keys(output.inputs)
63 .map((input) => path.relative(effectSrcDir, path.resolve(packageDir, input)))
64 .filter((module) => !module.startsWith(".."))
65 .sort()
66}
67
68describe("http-router fixture bundled with esbuild", () => {
69 // esbuild transpiles and Babel annotates every reachable Effect source module.
70 // Under full-suite Bun CI load this can exceed Vitest's 5s default.
71 it("does not include any Schema module", { timeout: 20_000 }, async () => {
72 const modules = await bundledEffectModules("http-router.ts")
73 assert.include(modules, "http/HttpRouter.ts")
74 assert.include(modules, "http/HttpServerRespondable.ts")
75 const schemaModules = modules.filter((module) =>
76 /^(?:Schema[A-Za-z]*\.ts|schema\/|internal\/schema\/)/.test(module)
77 )
78 assert.deepStrictEqual(schemaModules, [], `Schema modules reached the router bundle: ${schemaModules.join(", ")}`)
79 })
80})