Skip to content

Commit

Permalink
feat: einf start
Browse files Browse the repository at this point in the history
  • Loading branch information
ArcherGu committed Aug 16, 2022
1 parent 69e3c2d commit 536ffaf
Show file tree
Hide file tree
Showing 9 changed files with 317 additions and 2 deletions.
12 changes: 11 additions & 1 deletion .eslintrc.json
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"
}
}
]
}
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@
},
"dependencies": {
"check-package-exists": "^1.1.0",
"colorette": "^2.0.19"
"colorette": "^2.0.19",
"reflect-metadata": "^0.1.13"
},
"devDependencies": {
"@lightwing/eslint-config": "0.0.4",
Expand Down
6 changes: 6 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 13 additions & 0 deletions src/constants.ts
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'

51 changes: 51 additions & 0 deletions src/decorators.ts
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)
}
}
140 changes: 140 additions & 0 deletions src/index.ts
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}]`)
}
}
})
}
}
63 changes: 63 additions & 0 deletions src/log.ts
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)),
)
}
},
}
}
19 changes: 19 additions & 0 deletions tsconfig.json
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"
]
}
12 changes: 12 additions & 0 deletions tsup.config.ts
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,
})

0 comments on commit 536ffaf

Please sign in to comment.