-
-
Notifications
You must be signed in to change notification settings - Fork 1
/
plugin.ts
113 lines (103 loc) 路 3.68 KB
/
plugin.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
import {to_array} from '@ryanatkn/belt/array.js';
import type {Task_Context} from './task.js';
/**
* Gro `Plugin`s enable custom behavior during `gro dev` and `gro build`.
* In contrast, `Adapter`s use the results of `gro build` to produce final artifacts.
*/
export interface Plugin<T_Plugin_Context extends Plugin_Context = Plugin_Context> {
name: string;
setup?: (ctx: T_Plugin_Context) => void | Promise<void>;
adapt?: (ctx: T_Plugin_Context) => void | Promise<void>;
teardown?: (ctx: T_Plugin_Context) => void | Promise<void>;
}
export interface Create_Config_Plugins<T_Plugin_Context extends Plugin_Context = Plugin_Context> {
(
ctx: T_Plugin_Context,
):
| (Plugin<T_Plugin_Context> | null | Array<Plugin<T_Plugin_Context> | null>)
| Promise<Plugin<T_Plugin_Context> | null | Array<Plugin<T_Plugin_Context> | null>>;
}
export interface Plugin_Context<T_Args = object> extends Task_Context<T_Args> {
dev: boolean;
watch: boolean;
}
export class Plugins<T_Plugin_Context extends Plugin_Context> {
/* prefer `Plugins.create` to the constructor */
constructor(
private ctx: T_Plugin_Context,
private instances: Plugin[],
) {}
static async create<T_Plugin_Context extends Plugin_Context>(
ctx: T_Plugin_Context,
): Promise<Plugins<T_Plugin_Context>> {
const {timings} = ctx;
const timing_to_create = timings.start('plugins.create');
const instances: Plugin[] = to_array(await ctx.config.plugins(ctx)).filter(Boolean) as any;
const plugins = new Plugins(ctx, instances);
timing_to_create();
return plugins;
}
async setup(): Promise<void> {
const {ctx, instances} = this;
if (!instances.length) return;
const {timings, log} = ctx;
const timing_to_setup = timings.start('plugins.setup');
for (const plugin of instances) {
if (!plugin.setup) continue;
log.debug('setup plugin', plugin.name);
const timing = timings.start(`setup:${plugin.name}`);
await plugin.setup(ctx); // eslint-disable-line no-await-in-loop
timing();
}
timing_to_setup();
}
async adapt(): Promise<void> {
const {ctx, instances} = this;
const {timings} = ctx;
const timing_to_run_adapters = timings.start('plugins.adapt');
for (const plugin of instances) {
if (!plugin.adapt) continue;
const timing = timings.start(`adapt:${plugin.name}`);
await plugin.adapt(ctx); // eslint-disable-line no-await-in-loop
timing();
}
timing_to_run_adapters();
}
async teardown(): Promise<void> {
const {ctx, instances} = this;
if (!instances.length) return;
const {timings, log} = ctx;
const timing_to_teardown = timings.start('plugins.teardown');
for (const plugin of instances) {
if (!plugin.teardown) continue;
log.debug('teardown plugin', plugin.name);
const timing = timings.start(`teardown:${plugin.name}`);
await plugin.teardown(ctx); // eslint-disable-line no-await-in-loop
timing();
}
timing_to_teardown();
}
}
/**
* Replaces a plugin by name in `plugins` without mutating the param.
* Throws if the plugin name cannot be found.
* @param plugins - accepts the same types as the return value of `Create_Config_Plugins`
* @param new_plugin
* @param name - @default new_plugin.name
* @returns `plugins` with `new_plugin` at the index of the plugin with `name`
*/
export const replace_plugin = <
T_Plugins extends T_Plugin | null | Array<T_Plugin | null>,
T_Plugin extends Plugin,
>(
plugins: T_Plugins,
new_plugin: Plugin,
name = new_plugin.name,
): T_Plugin[] => {
const array = to_array(plugins).filter(Boolean) as Plugin[];
const index = array.findIndex((p) => p.name === name);
if (index === -1) throw Error('Failed to find plugin to replace: ' + name);
const replaced = array.slice();
replaced[index] = new_plugin;
return replaced as T_Plugin[];
};