Skip to content

Commit

Permalink
Support object form for banner/footer/intro/outro
Browse files Browse the repository at this point in the history
  • Loading branch information
lukastaegert committed Aug 5, 2022
1 parent 3bc931c commit c031c04
Show file tree
Hide file tree
Showing 5 changed files with 164 additions and 45 deletions.
28 changes: 13 additions & 15 deletions src/rollup/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -366,7 +366,7 @@ export interface OutputBundleWithPlaceholders {
[fileName: string]: OutputAsset | OutputChunk | FilePlaceholder;
}

export interface BasicPluginHooks {
export interface FunctionPluginHooks {
augmentChunkHash: (this: PluginContext, chunk: PreRenderedChunk) => string | void;
buildEnd: (this: PluginContext, err?: Error) => void;
buildStart: (this: PluginContext, options: NormalizedInputOptions) => void;
Expand Down Expand Up @@ -427,7 +427,7 @@ export type OutputPluginHooks =
| 'resolveImportMeta'
| 'writeBundle';

export type InputPluginHooks = Exclude<keyof BasicPluginHooks, OutputPluginHooks>;
export type InputPluginHooks = Exclude<keyof FunctionPluginHooks, OutputPluginHooks>;

export type SyncPluginHooks =
| 'augmentChunkHash'
Expand All @@ -437,7 +437,7 @@ export type SyncPluginHooks =
| 'resolveFileUrl'
| 'resolveImportMeta';

export type AsyncPluginHooks = Exclude<keyof BasicPluginHooks, SyncPluginHooks>;
export type AsyncPluginHooks = Exclude<keyof FunctionPluginHooks, SyncPluginHooks>;

export type FirstPluginHooks =
| 'load'
Expand All @@ -458,7 +458,7 @@ export type SequentialPluginHooks =
| 'transform';

export type ParallelPluginHooks = Exclude<
keyof BasicPluginHooks | AddonHooks,
keyof FunctionPluginHooks | AddonHooks,
FirstPluginHooks | SequentialPluginHooks
>;

Expand All @@ -473,25 +473,23 @@ type AllowEnforceOrderHook<T extends (...a: any) => any> =
| { handler: T; order?: 'pre' | 'post' | null };

export type PluginHooks = {
[K in keyof BasicPluginHooks]: K extends AsyncPluginHooks
? AllowEnforceOrderHook<MakeAsync<BasicPluginHooks[K]>>
: BasicPluginHooks[K];
[K in keyof FunctionPluginHooks]: K extends AsyncPluginHooks
? AllowEnforceOrderHook<MakeAsync<FunctionPluginHooks[K]>>
: FunctionPluginHooks[K];
};

export interface Plugin extends Partial<PluginHooks>, Partial<{ [K in AddonHooks]: AddonHook }> {
// for inter-plugin communication
api?: any;
cacheKey?: string;
name: string;
}

export interface OutputPlugin
extends Partial<{ [K in OutputPluginHooks]: PluginHooks[K] }>,
Partial<{ [K in AddonHooks]: AddonHook }> {
Partial<{ [K in AddonHooks]: AllowEnforceOrderHook<AddonHook> }> {
cacheKey?: string;
name: string;
}

export interface Plugin extends OutputPlugin, Partial<PluginHooks> {
// for inter-plugin communication
api?: any;
}

type TreeshakingPreset = 'smallest' | 'safest' | 'recommended';

export interface NormalizedTreeshakingOptions {
Expand Down
63 changes: 33 additions & 30 deletions src/utils/PluginDriver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ import type {
AddonHookFunction,
AddonHooks,
AsyncPluginHooks,
BasicPluginHooks,
EmitFile,
FirstPluginHooks,
FunctionPluginHooks,
NormalizedInputOptions,
NormalizedOutputOptions,
OutputBundleWithPlaceholders,
Expand Down Expand Up @@ -39,7 +39,7 @@ type EnsurePromise<T> = Promise<ResolveValue<T>>;
* Get the type of the first argument in a function.
* @example Arg0<(a: string, b: number) => void> -> string
*/
type Arg0<H extends keyof BasicPluginHooks> = Parameters<BasicPluginHooks[H]>[0];
type Arg0<H extends keyof FunctionPluginHooks> = Parameters<FunctionPluginHooks[H]>[0];

// This will make sure no input hook is omitted
const inputHookNames: {
Expand Down Expand Up @@ -136,11 +136,11 @@ export class PluginDriver {
// chains, first non-null result stops and returns
hookFirst<H extends AsyncPluginHooks & FirstPluginHooks>(
hookName: H,
args: Parameters<BasicPluginHooks[H]>,
args: Parameters<FunctionPluginHooks[H]>,
replaceContext?: ReplaceContext | null,
skipped?: ReadonlySet<Plugin> | null
): Promise<ReturnType<BasicPluginHooks[H]>> {
let promise: Promise<ReturnType<BasicPluginHooks[H]>> = Promise.resolve(undefined as any);
): Promise<ReturnType<FunctionPluginHooks[H]>> {
let promise: Promise<ReturnType<FunctionPluginHooks[H]>> = Promise.resolve(undefined as any);
for (const plugin of this.getSortedPlugins(hookName)) {
if (skipped && skipped.has(plugin)) continue;
promise = promise.then(result => {
Expand All @@ -154,9 +154,9 @@ export class PluginDriver {
// chains synchronously, first non-null result stops and returns
hookFirstSync<H extends SyncPluginHooks & FirstPluginHooks>(
hookName: H,
args: Parameters<BasicPluginHooks[H]>,
args: Parameters<FunctionPluginHooks[H]>,
replaceContext?: ReplaceContext
): ReturnType<BasicPluginHooks[H]> {
): ReturnType<FunctionPluginHooks[H]> {
for (const plugin of this.plugins) {
const result = this.runHookSync(hookName, args, plugin, replaceContext);
if (result != null) return result;
Expand All @@ -167,7 +167,7 @@ export class PluginDriver {
// parallel, ignores returns
hookParallel<H extends AsyncPluginHooks & ParallelPluginHooks>(
hookName: H,
args: Parameters<BasicPluginHooks[H]>,
args: Parameters<FunctionPluginHooks[H]>,
replaceContext?: ReplaceContext
): Promise<void> {
const promises: Promise<void>[] = [];
Expand All @@ -182,18 +182,18 @@ export class PluginDriver {
// chains, reduces returned value, handling the reduced value as the first hook argument
hookReduceArg0<H extends AsyncPluginHooks & SequentialPluginHooks>(
hookName: H,
[arg0, ...rest]: Parameters<BasicPluginHooks[H]>,
[arg0, ...rest]: Parameters<FunctionPluginHooks[H]>,
reduce: (
reduction: Arg0<H>,
result: ReturnType<BasicPluginHooks[H]>,
result: ReturnType<FunctionPluginHooks[H]>,
plugin: Plugin
) => Arg0<H>,
replaceContext?: ReplaceContext
): Promise<Arg0<H>> {
let promise = Promise.resolve(arg0);
for (const plugin of this.getSortedPlugins(hookName)) {
promise = promise.then(arg0 => {
const args = [arg0, ...rest] as Parameters<BasicPluginHooks[H]>;
const args = [arg0, ...rest] as Parameters<FunctionPluginHooks[H]>;
const hookPromise = this.runHook(hookName, args, plugin, false, replaceContext);
if (!hookPromise) return arg0;
return hookPromise.then(result =>
Expand All @@ -207,16 +207,16 @@ export class PluginDriver {
// chains synchronously, reduces returned value, handling the reduced value as the first hook argument
hookReduceArg0Sync<H extends SyncPluginHooks & SequentialPluginHooks>(
hookName: H,
[arg0, ...rest]: Parameters<BasicPluginHooks[H]>,
[arg0, ...rest]: Parameters<FunctionPluginHooks[H]>,
reduce: (
reduction: Arg0<H>,
result: ReturnType<BasicPluginHooks[H]>,
result: ReturnType<FunctionPluginHooks[H]>,
plugin: Plugin
) => Arg0<H>,
replaceContext?: ReplaceContext
): Arg0<H> {
for (const plugin of this.plugins) {
const args = [arg0, ...rest] as Parameters<BasicPluginHooks[H]>;
const args = [arg0, ...rest] as Parameters<FunctionPluginHooks[H]>;
const result = this.runHookSync(hookName, args, plugin, replaceContext);
arg0 = reduce.call(this.pluginContexts.get(plugin), arg0, result, plugin);
}
Expand All @@ -236,7 +236,7 @@ export class PluginDriver {
replaceContext?: ReplaceContext
): Promise<T> {
let promise = Promise.resolve(initialValue);
for (const plugin of this.plugins) {
for (const plugin of this.getSortedPlugins(hookName)) {
promise = promise.then(value => {
const hookPromise = this.runHook(hookName, args, plugin, true, replaceContext);
if (!hookPromise) return value;
Expand All @@ -252,8 +252,8 @@ export class PluginDriver {
hookReduceValueSync<H extends SyncPluginHooks & SequentialPluginHooks, T>(
hookName: H,
initialValue: T,
args: Parameters<BasicPluginHooks[H]>,
reduce: (reduction: T, result: ReturnType<BasicPluginHooks[H]>, plugin: Plugin) => T,
args: Parameters<FunctionPluginHooks[H]>,
reduce: (reduction: T, result: ReturnType<FunctionPluginHooks[H]>, plugin: Plugin) => T,
replaceContext?: ReplaceContext
): T {
let acc = initialValue;
Expand All @@ -267,7 +267,7 @@ export class PluginDriver {
// chains, ignores returns
hookSeq<H extends AsyncPluginHooks & SequentialPluginHooks>(
hookName: H,
args: Parameters<BasicPluginHooks[H]>,
args: Parameters<FunctionPluginHooks[H]>,
replaceContext?: ReplaceContext
): Promise<void> {
let promise = Promise.resolve();
Expand All @@ -279,7 +279,7 @@ export class PluginDriver {
return promise;
}

private getSortedPlugins(hookName: AsyncPluginHooks): Plugin[] {
private getSortedPlugins(hookName: AsyncPluginHooks | AddonHooks): Plugin[] {
return getOrCreate(this.sortedPlugins, hookName, () =>
getSortedPlugins(hookName, this.plugins)
);
Expand All @@ -302,22 +302,21 @@ export class PluginDriver {
): EnsurePromise<ReturnType<AddonHookFunction>>;
private runHook<H extends AsyncPluginHooks>(
hookName: H,
args: Parameters<BasicPluginHooks[H]>,
args: Parameters<FunctionPluginHooks[H]>,
plugin: Plugin,
permitValues: false,
hookContext?: ReplaceContext | null
): Promise<ReturnType<BasicPluginHooks[H]>>;
): Promise<ReturnType<FunctionPluginHooks[H]>>;
private runHook<H extends AsyncPluginHooks>(
hookName: H,
args: Parameters<BasicPluginHooks[H]>,
args: Parameters<FunctionPluginHooks[H]>,
plugin: Plugin,
permitValues: boolean,
hookContext?: ReplaceContext | null
): Promise<ReturnType<BasicPluginHooks[H]>> {
): Promise<ReturnType<FunctionPluginHooks[H]>> {
const hook = plugin[hookName];
if (!hook) return undefined as any;
const handler =
typeof hook === 'object' && typeof hook.handler === 'function' ? hook.handler : hook;
const handler = typeof hook === 'object' ? hook.handler : hook;

let context = this.pluginContexts.get(plugin)!;
if (hookContext) {
Expand All @@ -327,6 +326,7 @@ export class PluginDriver {
let action: [string, string, Parameters<any>] | null = null;
return Promise.resolve()
.then(() => {
// TODO Lukas move validation to sort function
// permit values allows values to be returned instead of a functional hook
if (typeof handler !== 'function') {
if (permitValues) return handler;
Expand All @@ -335,7 +335,7 @@ export class PluginDriver {
// eslint-disable-next-line @typescript-eslint/ban-types
const hookResult = (handler as Function).apply(context, args);

if (!hookResult || !hookResult.then) {
if (!hookResult?.then) {
// short circuit for non-thenables and non-Promises
return hookResult;
}
Expand Down Expand Up @@ -375,10 +375,10 @@ export class PluginDriver {
*/
private runHookSync<H extends SyncPluginHooks>(
hookName: H,
args: Parameters<BasicPluginHooks[H]>,
args: Parameters<FunctionPluginHooks[H]>,
plugin: Plugin,
hookContext?: ReplaceContext
): ReturnType<BasicPluginHooks[H]> {
): ReturnType<FunctionPluginHooks[H]> {
const hook = plugin[hookName];
if (!hook) return undefined as any;

Expand All @@ -401,14 +401,17 @@ export class PluginDriver {
}

// TODO Lukas we can do plugin hook validation in this function
export function getSortedPlugins(hookName: AsyncPluginHooks, plugins: readonly Plugin[]): Plugin[] {
export function getSortedPlugins(
hookName: AsyncPluginHooks | AddonHooks,
plugins: readonly Plugin[]
): Plugin[] {
const pre: Plugin[] = [];
const normal: Plugin[] = [];
const post: Plugin[] = [];
for (const plugin of plugins) {
const hook = plugin[hookName];
if (hook) {
if (typeof hook === 'object' && 'order' in hook) {
if (typeof hook === 'object') {
if (hook.order === 'pre') {
pre.push(plugin);
continue;
Expand Down
40 changes: 40 additions & 0 deletions test/form/samples/enforce-addon-order/_config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
const assert = require('assert');
const fs = require('fs');
const path = require('path');
const acorn = require('acorn');

const ID_MAIN = path.join(__dirname, 'main.js');
const code = fs.readFileSync(ID_MAIN, 'utf8');

const hooks = ['banner', 'footer', 'intro', 'outro'];

const plugins = [];
addPlugin(null);
addPlugin('pre');
addPlugin('post');
addPlugin('post');
addPlugin('pre');
addPlugin(undefined);
function addPlugin(order) {
const name = `${order}-${(plugins.length >> 1) + 1}`;
const plugin = { name };
const stringPlugin = { name: `string-${name}` };
for (const hook of hooks) {
plugin[hook] = {
order,
handler: () => `// ${hook}-${name}`
};
stringPlugin[hook] = {
order,
handler: `// ${hook}-string-${name}`
};
}
plugins.push(plugin, stringPlugin);
}

module.exports = {
description: 'allows to enforce addon order',
options: {
plugins
}
};

0 comments on commit c031c04

Please sign in to comment.