1import { defineConfig, type UserConfig } from "tsdown";
2import { DEFAULT_COMPATIBILITY_DATE } from "./src/core/internal/constants.ts";
3import {
4 InternalWorkerExportPlugin,
5 RuntimeSubpathExportPlugin,
6} from "./src/internal/build-tools/index.ts";
7import cloudflare from "./src/rolldown/plugin.ts";
8
9/**
10 * Internal workers are built per compatibility-flag set: the `cloudflare`
11 * plugin adjusts its bundling (Node.js polyfills, warnings) based on the
12 * flags, which apply to a whole build.
13 */
14const workerConfig = (
15 entry: Array<string> | Record<string, string>,
16 options: { compatibilityFlags?: Array<string>; clean?: boolean } = {},
17): UserConfig => ({
18 cwd: ".",
19 entry,
20 outDir: "dist/core/workers",
21 clean: options.clean,
22 tsconfig: "tsconfig.json",
23 format: "esm",
24 minify: {
25 mangle: false,
26 },
27 target: "esnext",
28 dts: false,
29 // The cloudflare/build-utils plugins are typed against the workspace's
30 // rolldown while tsdown's `UserConfig` is typed against its own nested
31 // copy; the structural compare between the two plugin types blows tsc's
32 // depth limit, so bridge through `unknown`.
33 plugins: [
34 cloudflare({
35 compatibilityDate: DEFAULT_COMPATIBILITY_DATE,
36 compatibilityFlags: options.compatibilityFlags,
37 externalRequire: false,
38 }),
39 InternalWorkerExportPlugin(),
40 ] as unknown as UserConfig["plugins"],
41 deps: {
42 neverBundle: [/^cloudflare-internal:/],
43 alwaysBundle: [/.+/],
44 },
45 outputOptions: {
46 entryFileNames: "[name].mjs",
47 },
48});
49
50export default defineConfig([
51 {
52 cwd: ".",
53 entry: ["src/rolldown/**/*.ts", "!src/rolldown/test/**/*.ts"],
54 exports: false,
55 outDir: "dist/rolldown",
56 tsconfig: "tsconfig.json",
57 unbundle: true,
58 dts: { incremental: false },
59 shims: false,
60 sourcemap: true,
61 target: "esnext",
62 format: "esm",
63 outputOptions: {
64 entryFileNames: "[name].js",
65 },
66 },
67 // `internal/shared.worker.ts` is a module shared between the workers, not a
68 // worker itself, so it isn't an entry: it's bundled into its importers (as
69 // a shared chunk when there are several).
70 workerConfig([
71 "src/core/**/*.worker.ts",
72 "!src/core/internal/shared.worker.ts",
73 "!src/core/**/R2Bucket.worker.ts",
74 ]),
75 // The R2 worker uses `node:crypto`; the 2026-08-31 date enables Node.js
76 // compatibility without a redundant flag. Don't
77 // clean the shared `dist/workers` directory, which the previous config
78 // already populated. The entry is named explicitly to keep the output at
79 // the path `worker:` imports resolve to.
80 workerConfig(
81 {
82 "bindings/r2-bucket/R2Bucket.worker":
83 "src/core/bindings/r2-bucket/R2Bucket.worker.ts",
84 },
85 { clean: false },
86 ),
87 {
88 cwd: ".",
89 entry: [
90 "src/core/**/*.ts",
91 "!src/core/**/*.worker.ts",
92 "!src/core/globals.d.ts",
93 "!src/core/scripts/**/*.ts",
94 "!src/core/test/**/*.ts",
95 ],
96 // The consolidated package owns a hand-authored, prefixed export map.
97 // Component builds must never rewrite the root package.json.
98 exports: false,
99 outDir: "dist/core/node",
100 tsconfig: "tsconfig.json",
101 unbundle: true,
102 dts: { incremental: false },
103 shims: false,
104 target: "esnext",
105 format: "esm",
106 inputOptions: {
107 external: [/^#cloudflare-runtime-/],
108 makeAbsoluteExternalsRelative: true,
109 },
110 outputOptions: {
111 entryFileNames: (chunkInfo) => {
112 const name = chunkInfo.name.replace(
113 /(^node_modules\/.+\/node_modules\/)|(^packages\/vendor\/)/,
114 "vendor/",
115 );
116 return `${name}.${name.endsWith(".d") ? "mts" : "mjs"}`;
117 },
118 },
119 },
120 {
121 cwd: ".",
122 entry: ["src/vite/**/*.worker.ts"],
123 outDir: "dist/vite/workers",
124 tsconfig: "tsconfig.json",
125 format: "esm",
126 minify: {
127 mangle: false,
128 },
129 plugins: [
130 cloudflare({
131 compatibilityDate: DEFAULT_COMPATIBILITY_DATE,
132 // These workers are exported as single modules, so they cannot carry
133 // the helper chunk emitted by the external-require rewrite.
134 externalRequire: false,
135 }),
136 InternalWorkerExportPlugin(),
137 ],
138 deps: {
139 alwaysBundle: [/.+/],
140 },
141 dts: false,
142 outputOptions: {
143 entryFileNames: "[name].mjs",
144 },
145 },
146 {
147 cwd: ".",
148 entry: ["src/vite/plugin.ts"],
149 exports: false,
150 outDir: "dist/vite/node",
151 tsconfig: "tsconfig.json",
152 dts: { incremental: false },
153 shims: false,
154 target: "esnext",
155 format: "esm",
156 plugins: [RuntimeSubpathExportPlugin()],
157 deps: {
158 // These are subpaths of this consolidated package. Keep them as public
159 // imports so the Vite declaration bundle shares the core runtime types.
160 neverBundle: [/^@alchemy\.run\/cloudflare-runtime(?:\/|$)/],
161 },
162 inputOptions: {
163 external: [
164 /^#cloudflare-runtime-/,
165 /^@alchemy\.run\/cloudflare-runtime(?:\/|$)/,
166 ],
167 makeAbsoluteExternalsRelative: true,
168 },
169 outputOptions: {
170 exports: "named",
171 },
172 },
173]);