1import type * as rolldown from "rolldown";
2import type * as vite from "vite";
3import { isSkippedEnvironment, type BasePluginOptions } from "./options.ts";
4
5export interface PluginInput<A = any> {
6 shared?: Omit<rolldown.Plugin<A>, "name">;
7 rolldown?: Omit<rolldown.Plugin<A>, "name">;
8 vite?: Omit<vite.Plugin<A>, "name">;
9}
10
11export interface NullablePluginOutput<A = any> {
12 rolldown: (options: BasePluginOptions) => rolldown.Plugin<A> | null;
13 vite: (options: BasePluginOptions) => vite.Plugin<A> | null;
14}
15
16export interface PluginOutput<A = any> {
17 rolldown: (options: BasePluginOptions) => rolldown.Plugin<A>;
18 vite: (options: BasePluginOptions) => vite.Plugin<A>;
19}
20
21export function createPlugin<TName extends string, A = any>(
22 pluginName: TName,
23 make: (options: BasePluginOptions) => PluginInput<A>,
24): PluginOutput<A>;
25
26export function createPlugin<TName extends string, A = any>(
27 pluginName: TName,
28 make: (options: BasePluginOptions) => PluginInput<A> | undefined,
29): NullablePluginOutput<A>;
30
31export function createPlugin<TName extends string, A = any>(
32 pluginName: TName,
33 make: (options: BasePluginOptions) => PluginInput<A> | undefined,
34): PluginOutput<A> | NullablePluginOutput<A> {
35 const name = `distilled-cloudflare:${pluginName}`;
36 return {
37 rolldown: (options: BasePluginOptions) => {
38 const plugin = make(options);
39 if (!plugin) return null;
40 return {
41 name,
42 ...plugin.shared,
43 ...plugin.rolldown,
44 };
45 },
46 vite: (options: BasePluginOptions) => {
47 const plugin = make(options);
48 if (!plugin) return null;
49 const vitePlugin = {
50 name,
51 sharedDuringBuild: true,
52 ...plugin.shared,
53 ...plugin.vite,
54 } as vite.Plugin;
55 // Scope every per-environment hook (resolveId/load/transform/...) away
56 // from the browser `client` environment and any framework-owned
57 // Node-side environments listed in `skipEnvironments` (e.g. Astro's
58 // `astro` and `prerender` environments).
59 const applyToEnvironment = vitePlugin.applyToEnvironment;
60 vitePlugin.applyToEnvironment = function (environment) {
61 if (isSkippedEnvironment(options, environment.name)) return false;
62 return applyToEnvironment
63 ? applyToEnvironment.call(this, environment)
64 : true;
65 };
66 // `applyToEnvironment` only gates the per-environment plugin pipeline;
67 // `configEnvironment` runs during config resolution for every
68 // environment, so skipped environments must be filtered explicitly.
69 const configEnvironment = vitePlugin.configEnvironment;
70 if (configEnvironment && options.skipEnvironments?.length) {
71 const handler =
72 typeof configEnvironment === "function"
73 ? configEnvironment
74 : configEnvironment.handler;
75 const wrapped: typeof handler = function (environmentName, ...args) {
76 if (options.skipEnvironments?.includes(environmentName)) return;
77 return handler.call(this, environmentName, ...args);
78 };
79 vitePlugin.configEnvironment =
80 typeof configEnvironment === "function"
81 ? wrapped
82 : { ...configEnvironment, handler: wrapped };
83 }
84 return vitePlugin;
85 },
86 };
87}