| 1 | import type * as rolldown from "rolldown"; |
| 2 | import type * as vite from "vite"; |
| 3 | import { isSkippedEnvironment, type BasePluginOptions } from "./options.ts"; |
| 4 | |
| 5 | export 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 | |
| 11 | export interface NullablePluginOutput<A = any> { |
| 12 | rolldown: (options: BasePluginOptions) => rolldown.Plugin<A> | null; |
| 13 | vite: (options: BasePluginOptions) => vite.Plugin<A> | null; |
| 14 | } |
| 15 | |
| 16 | export interface PluginOutput<A = any> { |
| 17 | rolldown: (options: BasePluginOptions) => rolldown.Plugin<A>; |
| 18 | vite: (options: BasePluginOptions) => vite.Plugin<A>; |
| 19 | } |
| 20 | |
| 21 | export function createPlugin<TName extends string, A = any>( |
| 22 | pluginName: TName, |
| 23 | make: (options: BasePluginOptions) => PluginInput<A>, |
| 24 | ): PluginOutput<A>; |
| 25 | |
| 26 | export function createPlugin<TName extends string, A = any>( |
| 27 | pluginName: TName, |
| 28 | make: (options: BasePluginOptions) => PluginInput<A> | undefined, |
| 29 | ): NullablePluginOutput<A>; |
| 30 | |
| 31 | export 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 | } |