Skip to content

Commit

Permalink
feat: reset types definitions and dependencies for Application class (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
Repraance authored Sep 11, 2021
1 parent 754ed12 commit 24614b1
Show file tree
Hide file tree
Showing 66 changed files with 1,266 additions and 170 deletions.
17 changes: 14 additions & 3 deletions packages/app/core/client/application.d.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
import { Runtime } from '@shuvi/types';

export const create: Runtime.ApplicationCreater;
import { IAppRenderFn, IApplication } from '@shuvi/runtime-core'
export interface IApplicationCreaterContext {
routeProps?: { [x: string]: any };
[x: string]: any;
}
export interface ApplicationCreater {
(
context: IApplicationCreaterContext,
options: {
render: IAppRenderFn;
}
): IApplication;
}
export const create: ApplicationCreater;
1 change: 1 addition & 0 deletions packages/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"node": ">= 12.0.0"
},
"dependencies": {
"@shuvi/runtime-core": "^0.0.1-rc.32",
"@shuvi/types": "^0.0.1-rc.32"
}
}
4 changes: 4 additions & 0 deletions packages/hook/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
*.log
.DS_Store
node_modules
dist
1 change: 1 addition & 0 deletions packages/hook/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# @shuvi/hook
26 changes: 26 additions & 0 deletions packages/hook/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"name": "@shuvi/hook",
"version": "0.0.1-rc.32",
"license": "MIT",
"main": "lib/index.js",
"module": "esm/index.js",
"types": "lib/index.d.ts",
"files": [
"dist",
"src"
],
"engines": {
"node": ">= 12.0.0"
},
"scripts": {
"dev": "run-p watch:*",
"watch:esm": "tsc -p tsconfig.build.esm.json -w",
"watch:cjs": "tsc -p tsconfig.build.cjs.json -w",
"prebuild": "rimraf lib esm",
"build": "run-p build:*",
"build:esm": "tsc -p tsconfig.build.esm.json",
"build:cjs": "tsc -p tsconfig.build.cjs.json"
},
"author": "Zheng Yu Tay",
"dependencies": {}
}
9 changes: 9 additions & 0 deletions packages/hook/src/AsyncParallelHook.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { IHookOpts } from './types';

export const executeAsyncParallelHook = async (
tapFns: IHookOpts['fn'][],
...args: any[]
) => {
const results = await Promise.all(tapFns.map(fn => fn(...args)));
return results;
};
21 changes: 21 additions & 0 deletions packages/hook/src/AsyncSeriesBailHook.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { IHookOpts } from './types';

export const executeAsyncSeriesBailHook = async (
tapFns: IHookOpts['fn'][],
...args: any[]
) => {
let result: unknown = [];

for (let i = 0; i < tapFns.length; i++) {
result = tapFns[i](...args);

if (Promise.resolve(result) === result) {
result = await result;
}

if (result) {
break;
}
}
return result;
};
14 changes: 14 additions & 0 deletions packages/hook/src/AsyncSeriesHook.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { IHookOpts } from './types';

export const executeAsyncSeriesHook = async (
tapFns: IHookOpts['fn'][],
...args: any[]
) => {
let results: unknown[] = [];

for (let i = 0; i < tapFns.length; i++) {
results.push(await tapFns[i](...args));
}

return results;
};
22 changes: 22 additions & 0 deletions packages/hook/src/AsyncSeriesWaterfallHook.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { IHookOpts } from './types';

export const executeAsyncSeriesWaterfallHook = async (
tapFns: IHookOpts['fn'][],
...args: any[]
) => {
for (let i = 0; i < tapFns.length; i++) {
let fn = tapFns[i];
let promiseResult = await fn(...args);
if (typeof args[0] !== 'undefined') {
if (typeof promiseResult !== 'undefined') {
args[0] = promiseResult;
} else {
console.warn(
`Expected return value from hook "${fn.hookName}" but is undefined`
);
}
}
}

return args[0];
};
119 changes: 119 additions & 0 deletions packages/hook/src/Hookable.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import { executeAsyncParallelHook } from './AsyncParallelHook';
import { executeAsyncSeriesHook } from './AsyncSeriesHook';
import { executeAsyncSeriesBailHook } from './AsyncSeriesBailHook';
import { executeAsyncSeriesWaterfallHook } from './AsyncSeriesWaterfallHook';

import { IHookOpts, ICallHookOpts, IHookable, IHookConfig } from './types';
import { insertHook, getHooksFunctions, removeHook } from './utils';

async function callSerailWithInitialValue<R = unknown>(
hooks: IHookOpts[],
args: any[],
initialValue: R
): Promise<R> {
const fns = getHooksFunctions(hooks);

return executeAsyncSeriesWaterfallHook(fns, initialValue, ...args);
}

async function callSerail<R = unknown>(
hooks: IHookOpts[],
args: any[],
bail: boolean
): Promise<R> {
const thookFn = bail ? executeAsyncSeriesBailHook : executeAsyncSeriesHook;
const fns = getHooksFunctions(hooks);
return thookFn(fns, ...args) as Promise<R>;
}

async function callParallel<R = unknown>(
hooks: IHookOpts[],
args: any[]
): Promise<R> {
const fns = getHooksFunctions(hooks);

return (await (executeAsyncParallelHook(fns, ...args) as any)) as Promise<R>;
}

export class Hookable implements IHookable {
private _hooks = new Map<string, IHookOpts[]>();

tap<Config extends IHookConfig = IHookConfig>(
name: Config['name'],
hook: IHookOpts<Config['initialValue'], Config['args']>
) {
let hooks = this._hooks.get(name);
if (!hooks) {
hooks = [];
this._hooks.set(name, hooks);
}

insertHook(hooks, hook);

return () => {
removeHook(hooks!, hook);
};
}

callHook<Config extends IHookConfig = IHookConfig>(
name: Config['name'],
...args: Config['args']
): Promise<unknown[]>;
callHook<Config extends IHookConfig = IHookConfig>(
options: ICallHookOpts<Config['name'], Config['initialValue']>,
...args: Config['args']
): Promise<Config['initialValue']>;
// implement
async callHook(
options: string | ICallHookOpts<string>,
...args: any[]
): Promise<any> {
const defaultOpts = {
bail: false,
parallel: false,
initialValue: undefined
};
let opts: Required<ICallHookOpts>;
if (typeof options === 'object') {
opts = {
...defaultOpts,
...options
};
} else {
opts = {
...defaultOpts,
name: options
};
}

const hasInitialValue = typeof opts.initialValue !== 'undefined';

const hooks = this._hooks.get(opts.name);
if (!hooks || hooks.length <= 0) {
// @ts-ignore no return value
return hasInitialValue ? opts.initialValue : [];
}

if (opts.parallel) {
return await callParallel(hooks, args);
} else if (hasInitialValue) {
return await callSerailWithInitialValue(hooks, args, opts.initialValue);
} else {
return await callSerail(hooks, args, opts.bail);
}
}

on<Config extends IHookConfig = IHookConfig>(
event: Config['name'],
listener: (...args: Config['args']) => void
) {
return this.tap<any>(event, { name: 'listener', fn: listener });
}

emitEvent<Config extends IHookConfig = IHookConfig>(
name: Config['name'],
...args: Config['args']
) {
this.callHook({ name, parallel: true }, ...args);
}
}
3 changes: 3 additions & 0 deletions packages/hook/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { Hookable } from './Hookable';
export { Hookable };
export * from './types';
78 changes: 78 additions & 0 deletions packages/hook/src/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
export type NoInitValue = '$$no-initial-value';

export interface ICallHookOpts<Name extends string = string, InitV = unknown> {
name: Name;
bail?: boolean;
parallel?: boolean;
initialValue?: InitV;
}

export interface IHookOpts<
InitValue = NoInitValue,
Args extends any[] = any[]
> {
name: string;
fn: (InitValue extends NoInitValue
? (...args: Args) => void | Promise<void>
: (init: InitValue, ...args: Args) => InitValue | Promise<InitValue>) & {
hookName?: string;
};
before?: string;
stage?: number;
}

export interface IHookConfig {
name: string;
args: any[];
initialValue: any;
}

export interface IHookable {
tap<Config extends IHookConfig>(
hook: Config['name'],
opts: IHookOpts<Config['initialValue'], Config['args']>
): void;
callHook<Config extends IHookConfig>(
name: Config['name'],
...args: Config['args']
): Promise<unknown[]>;
callHook<Config extends IHookConfig>(
options: ICallHookOpts<Config['name'], Config['initialValue']>,
...args: Config['args']
): Promise<Config['initialValue']>;

on<Config extends IHookConfig>(
event: Config['name'],
listener: (...args: Config['args']) => void
): void;
emitEvent<Config extends IHookConfig>(
name: Config['name'],
...args: Config['args']
): void;
}

type IDefaultHookConfig = {
args: [];
initialValue: NoInitValue;
};

export type defineHook<
Name extends string,
Config extends Partial<IHookConfig> = {}
> = {
name: Name;
} & {
[K in keyof Config]: Config[K];
} &
{
[K in Exclude<
keyof IDefaultHookConfig,
keyof Config
>]: IDefaultHookConfig[K];
};

export type defineEvent<Name extends string, Args = []> = {
name: Name;
args: Args;
initialValue: NoInitValue;
};
56 changes: 56 additions & 0 deletions packages/hook/src/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { IHookOpts } from './types';

export const getHooksFunctions = (hooks: IHookOpts[]) => {
return hooks.map(({ fn, name }) => {
fn.hookName = name;
return fn;
});
};

// mutable sort
export const insertHook = (hooks: IHookOpts[], hook: IHookOpts) => {
let before;

if (typeof hook.before === 'string') {
before = new Set([hook.before]);
}

let stage = 0;
if (typeof hook.stage === 'number') {
stage = hook.stage;
}

const originalHooksLength = hooks.length;

if (hooks.length > 1) {
for (let i = 1; i < originalHooksLength; i++) {
const tap = hooks[i];
const tapStage = tap.stage || 0;

if (before) {
if (before.has(tap.name)) {
hooks.splice(i, 0, hook);
break;
}
}
if (tapStage > stage) {
hooks.splice(i, 0, hook);
break;
}
}
}

if (hooks.length === originalHooksLength) {
hooks.push(hook);
}

return hooks;
};

// mutable way
export const removeHook = (hooks: IHookOpts[], hookToRemove: IHookOpts) => {
const indexToRemove = hooks.findIndex(hook => hook === hookToRemove);
if (indexToRemove >= 0) {
hooks.splice(indexToRemove, 1);
}
};
Loading

0 comments on commit 24614b1

Please sign in to comment.