-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
9 changed files
with
317 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,13 @@ | ||
{ | ||
"extends": "@lightwing" | ||
"extends": "@lightwing", | ||
"overrides": [ | ||
{ | ||
"files": [ | ||
"src/log.ts" | ||
], | ||
"rules": { | ||
"no-console": "off" | ||
} | ||
} | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
export const PARAMTYPES_METADATA = 'design:paramtypes' | ||
export const INJECTABLE = 'injectable' | ||
export const INJECT_NAME = 'inject:name' | ||
export const INJECT_TYPE = { | ||
CLASS: 'class', | ||
CUSTOM: 'custom', | ||
WINDOW: 'window', | ||
} | ||
export const IPC_INVOKE = 'ipc:invoke' | ||
export const IPC_ON = 'ipc:on' | ||
export const IPC_WIN_NAME = 'ipc:win-name' | ||
export const DEFAULT_WIN_NAME = 'main' | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import { DEFAULT_WIN_NAME, INJECTABLE, INJECT_NAME, INJECT_TYPE, IPC_INVOKE, IPC_ON, IPC_WIN_NAME, PARAMTYPES_METADATA } from './constants' | ||
|
||
export function IpcInvoke(event: string): MethodDecorator { | ||
if (!event) | ||
throw new Error('ipc invoke event is required') | ||
|
||
return (target, propertyName) => { | ||
Reflect.defineMetadata(IPC_INVOKE, event, target, propertyName) | ||
} | ||
} | ||
|
||
export function IpcOn(event: string, name: string = DEFAULT_WIN_NAME): MethodDecorator { | ||
if (!event) | ||
throw new Error('ipc on event is required') | ||
|
||
return (target, propertyName) => { | ||
Reflect.defineMetadata(IPC_ON, event, target, propertyName) | ||
Reflect.defineMetadata(IPC_WIN_NAME, name, target, propertyName) | ||
} | ||
} | ||
|
||
export function Controller(): ClassDecorator { | ||
return (_) => { | ||
// do nothing | ||
} | ||
} | ||
|
||
export function Injectable(): ClassDecorator { | ||
return (target) => { | ||
Reflect.defineMetadata(INJECTABLE, INJECT_TYPE.CLASS, target) | ||
} | ||
} | ||
|
||
export function Inject(name: string): ParameterDecorator { | ||
if (!name) | ||
throw new Error('inject name is required') | ||
|
||
return (target, _, index) => { | ||
const param = Reflect.getMetadata(PARAMTYPES_METADATA, target)[index] | ||
Reflect.defineMetadata(INJECTABLE, INJECT_TYPE.CUSTOM, param) | ||
Reflect.defineMetadata(INJECT_NAME, name, param) | ||
} | ||
} | ||
|
||
export function Window(name = DEFAULT_WIN_NAME): ParameterDecorator { | ||
return (target, _, index) => { | ||
const param = Reflect.getMetadata(PARAMTYPES_METADATA, target)[index] | ||
Reflect.defineMetadata(INJECTABLE, INJECT_TYPE.WINDOW, param) | ||
Reflect.defineMetadata(INJECT_NAME, name, param) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
import 'reflect-metadata' | ||
import type { BrowserWindow } from 'electron' | ||
import { ipcMain } from 'electron' | ||
import { checkPackageExists } from 'check-package-exists' | ||
import { DEFAULT_WIN_NAME, INJECTABLE, INJECT_NAME, INJECT_TYPE, IPC_INVOKE, IPC_ON, IPC_WIN_NAME, PARAMTYPES_METADATA } from './constants' | ||
import { createLogger } from './log' | ||
export * from './decorators' | ||
|
||
type Construct<T = any> = new (...args: Array<any>) => T | ||
export type ControllerClass = Construct | ||
export type InjectableClass = Construct | ||
|
||
export interface WindowOpts { | ||
name: string | ||
win: BrowserWindow | ||
} | ||
|
||
export interface InjectableOpts { | ||
name: string | ||
inject: any | ||
} | ||
|
||
export interface Options { | ||
window: WindowOpts[] | (() => WindowOpts | Promise<WindowOpts>)[] | BrowserWindow | (() => BrowserWindow | Promise<BrowserWindow>) | ||
controllers: ControllerClass[] | ||
injects?: InjectableOpts[] | ||
} | ||
|
||
export async function createEinf({ window, controllers, injects = [] }: Options) { | ||
if (!checkPackageExists('electron')) | ||
throw new Error('Einf is a electron framework, please install electron first') | ||
|
||
const logger = createLogger() | ||
// init windows | ||
let windows: WindowOpts[] = [] | ||
if (Array.isArray(window)) { | ||
for (const win of window) | ||
windows.push(typeof win === 'function' ? (await win()) : win) | ||
} | ||
else { | ||
windows = [typeof window === 'function' ? ({ name: DEFAULT_WIN_NAME, win: (await window()) }) : ({ name: DEFAULT_WIN_NAME, win: window })] | ||
} | ||
|
||
const existInjectableClass: Record<string, any> = {} | ||
/** | ||
* factory controller/injectable | ||
*/ | ||
function factory<T>(constructClass: Construct<T>): T { | ||
const paramtypes: any[] = Reflect.getMetadata(PARAMTYPES_METADATA, constructClass) | ||
let providers: any[] = [] | ||
if (paramtypes) { | ||
providers = paramtypes.map((provider: Construct<T> | any, index) => { | ||
const injectType = Reflect.getMetadata(INJECTABLE, provider) | ||
if (injectType === INJECT_TYPE.CLASS) { | ||
const { name } = provider | ||
const item = existInjectableClass[name] || factory(provider) | ||
existInjectableClass[name] = item | ||
return item | ||
} | ||
else if (injectType === INJECT_TYPE.CUSTOM) { | ||
const name = Reflect.getMetadata(INJECT_NAME, provider) | ||
const injectInfo = injects.find(item => item.name === name) | ||
if (!injectInfo) | ||
throw new Error(`${name} is not provided to inject`) | ||
|
||
return injectInfo.inject | ||
} | ||
else if (injectType === INJECT_TYPE.WINDOW) { | ||
const name = Reflect.getMetadata(INJECT_NAME, provider) | ||
const winOpt = windows.find(item => item.name === name) | ||
|
||
if (!winOpt) | ||
throw new Error(`${name} is not provided to inject`) | ||
|
||
return winOpt.win | ||
} | ||
else { | ||
throw new Error(`${constructClass.name}'s parameter [${index}] is not injectable`) | ||
} | ||
}) | ||
} | ||
|
||
// eslint-disable-next-line new-cap | ||
return new constructClass(...providers) | ||
} | ||
|
||
// init controllers | ||
for (const ControllerClass of controllers) { | ||
const controller = factory(ControllerClass) | ||
const proto = ControllerClass.prototype | ||
const funcs = Object.getOwnPropertyNames(proto).filter( | ||
item => typeof controller[item] === 'function' && item !== 'constructor', | ||
) | ||
|
||
funcs.forEach((funcName) => { | ||
let event: string | null = null | ||
event = Reflect.getMetadata(IPC_INVOKE, proto, funcName) | ||
if (event) { | ||
ipcMain.handle(event, async (_, ...args) => { | ||
try { | ||
// eslint-disable-next-line prefer-spread | ||
const result = await controller[funcName].apply(controller, args) | ||
|
||
return { | ||
data: result, | ||
} | ||
} | ||
catch (error) { | ||
logger.error(error) | ||
return { | ||
data: undefined, | ||
error, | ||
} | ||
} | ||
}) | ||
} | ||
else { | ||
event = Reflect.getMetadata(IPC_ON, proto, funcName) | ||
if (!event) | ||
return | ||
|
||
const winName = Reflect.getMetadata(IPC_WIN_NAME, proto, funcName) | ||
const winInfo = windows.find(item => item.name === winName) | ||
if (winInfo) { | ||
const { webContents } = winInfo.win | ||
const func = controller[funcName] | ||
|
||
controller[funcName] = async (...args: any[]) => { | ||
const result = await func.apply(controller, args) | ||
webContents.send(event!, result) | ||
return result | ||
} | ||
} | ||
else { | ||
logger.warn(`Can not find window [${winName}] to emit event [${event}]`) | ||
} | ||
} | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
// Modified from https://github.com/egoist/tsup/blob/main/src/log.ts | ||
import * as colors from 'colorette' | ||
|
||
type LOG_TYPE = 'info' | 'success' | 'error' | 'warn' | ||
|
||
export const colorize = (type: LOG_TYPE, data: any) => { | ||
const color | ||
= type === 'info' | ||
? 'blue' | ||
: type === 'error' | ||
? 'red' | ||
: type === 'warn' | ||
? 'yellow' | ||
: 'green' | ||
return colors[color](data) | ||
} | ||
|
||
export type Logger = ReturnType<typeof createLogger> | ||
|
||
export const createLogger = () => { | ||
return { | ||
success(...args: any[]) { | ||
return this.print('success', ...args) | ||
}, | ||
|
||
info(...args: any[]) { | ||
return this.print('info', ...args) | ||
}, | ||
|
||
error(...args: any[]) { | ||
return this.print('error', ...args) | ||
}, | ||
|
||
warn(...args: any[]) { | ||
return this.print('warn', ...args) | ||
}, | ||
|
||
log(...args: any[]) { | ||
console.log(...args) | ||
}, | ||
|
||
break() { | ||
console.log('\n') | ||
}, | ||
|
||
print( | ||
type: 'info' | 'success' | 'error' | 'warn', | ||
...data: unknown[] | ||
) { | ||
switch (type) { | ||
case 'error': { | ||
return console.log( | ||
...data.map(item => colorize(type, item)), | ||
) | ||
} | ||
default: | ||
console.log( | ||
...data.map(item => colorize(type, item)), | ||
) | ||
} | ||
}, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
{ | ||
"compilerOptions": { | ||
"module": "CommonJS", | ||
"target": "ESNext", | ||
"outDir": "./dist", | ||
"strict": true, | ||
"moduleResolution": "node", | ||
"esModuleInterop": true, | ||
"skipLibCheck": true, | ||
"forceConsistentCasingInFileNames": true | ||
}, | ||
"include": [ | ||
"src/**/*" | ||
], | ||
"exclude": [ | ||
"node_modules", | ||
"dist" | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import { defineConfig } from 'tsup' | ||
|
||
export default defineConfig({ | ||
name: 'einf', | ||
entry: ['src/index.ts'], | ||
dts: { | ||
resolve: true, | ||
entry: 'src/index.ts', | ||
}, | ||
clean: true, | ||
splitting: true, | ||
}) |