使用 TypeScript 从零搭建自己的 Web 框架: Electron 环境运行
作者:RoyLin
- 2024-04-16 江西
本文字数:3028 字
阅读完需:约 10 分钟
得益于我们框架中引入了依赖注入模式,并通过接口将核心类与组件优雅地解耦,我们仅需稍作调整,便能够轻松地在 Electron 环境中运行它。
创建 Electron 开发环境
通过使用 Electron Forge CLI 项目创建一个 Electron 项目
npm init electron-app@latest my-new-app -- --template=webpack-typescript
复制代码
然后我们依然需要安装 reflect-metadata,以及修改 tsconfig 配置。
// tsconfig.josn
{
"compilerOptions": {
...
"experimentalDecorators": true /* 启用装饰器 */,
"emitDecoratorMetadata": true /* 发射装饰器元数据 */
}
...
}
复制代码
优化核心类
在 index.ts 入口文件中引入 reflect-metadata,同时为了简单起见我们修改框架的核心类,将之前自动扫描文件和导入的逻辑提取到外部实现,修改之前的 Application 的 listen 方法名为 start,以显得更加通用,紧接着我们实现一个 TRPCServer 组件用于替换之前的 WebServer 组件,最后在框架初始化成功后再创建 Electron 窗口。
// core/Application.ts
// ...
async initialize(resolvers: Constructor[] = []): Promise<this> {
for (let resolver of resolvers) {
const options = Reflect.getMetadata(INJECTABLE_METADATA, resolver);
if (options) {
this.container.register(resolver, { resolver, options });
const isController = Reflect.getMetadata(CONTROLLER_METADATA, resolver);
if (isController) {
this.mapRoutes(resolver);
}
}
}
return this;
}
// ...
// index.ts
// ...
const createWindow = async () => {
const framework = await new Application({
server: new TRPCServer(),
}).initialize([HomeService, HomeController]);
framework.start(async () => {
const mainWindow = new BrowserWindow({
height: 600,
width: 800,
webPreferences: {
preload: MAIN_WINDOW_PRELOAD_WEBPACK_ENTRY,
},
});
mainWindow.loadURL(MAIN_WINDOW_WEBPACK_ENTRY);
mainWindow.webContents.openDevTools();
});
};
// ...
复制代码
控制器和服务
// controller/HomeController.ts
import { Controller, Get } from '@/core';
import { HomeService } from '@/service/HomeService';
@Controller()
export class HomeController {
constructor(private readonly homeService: HomeService) {}
@Get('hello')
async index() {
return await this.homeService.sayHello();
}
}
// service/HomeService.ts
import { Injectable } from '@/core';
@Injectable()
export class HomeService {
constructor() {}
async sayHello(): Promise<string> {
return 'Hello from HomeService';
}
}
复制代码
实现 TRPCServer
简单实现一个 TRPCServer, 并将控制器方法与 tRPC 路由绑定。
// core/component/TRPCServer.ts
import { initTRPC } from '@trpc/server';
import { HTTPRequest, resolveHTTPResponse } from '@trpc/server/http';
import { ipcMain } from 'electron';
import SuperJSON from 'superjson';
import { IServer } from '../interface';
import { Route } from '../type';
export class TRPCServer implements IServer {
private readonly instance;
private routes: any = {};
constructor() {
this.instance = initTRPC.context<any>().create({
transformer: SuperJSON,
});
}
route(route: Route<any>) {
this.routes = {
...this.routes,
[route.path]: this.instance.procedure.query(route.handler),
};
return this;
}
async start(callback: any): Promise<any> {
ipcMain.handle('trpc', (_, req: IpcRequest) => {
return this.ipcRequestHandler({
endpoint: '/trpc',
req,
router: this.instance.router(this.routes),
});
});
callback();
}
private async ipcRequestHandler<TRouter extends any>(opts: {
req: IpcRequest;
router: TRouter;
batching?: { enabled: boolean };
onError?: (o: { error: Error; req: IpcRequest }) => void;
endpoint: string;
}): Promise<IpcResponse> {
// adding a fake "https://electron" to the URL so it can be parsed
const url = new URL('https://electron' + opts.req.url);
const path = url.pathname.slice(opts.endpoint.length + 1);
const req: HTTPRequest = {
query: url.searchParams,
method: opts.req.method,
headers: opts.req.headers,
body: opts.req.body,
};
const result = await resolveHTTPResponse({
req,
createContext: async () => {},
path,
// @ts-ignore
router: opts.router,
batching: opts.batching,
onError(o) {
opts?.onError?.({ ...o, req: opts.req });
},
});
return {
body: result.body,
headers: result.headers,
status: result.status,
};
}
}
复制代码
在 preload.ts 中将 trpc 方法暴露给渲染进程
// preload.ts
import { contextBridge, ipcRenderer } from 'electron';
process.once('loaded', async () => {
contextBridge.exposeInMainWorld('API', {
trpc: (req: IpcRequest) => ipcRenderer.invoke('trpc', req),
});
});
复制代码
在渲染进程中调用方法
// renderer.ts
import { createTRPCProxyClient, httpLink } from '@trpc/client';
import SuperJSON from 'superjson';
import './index.css';
console.log('👋 This message is being logged by "renderer.js", included via webpack');
const trpc = createTRPCProxyClient<any>({
links: [
httpLink({
url: '/trpc',
fetch: async (input, init) => {
const req = {
url: input instanceof URL ? input.toString() : typeof input === 'string' ? input : input.url,
method: input instanceof Request ? input.method : init?.method!,
headers: input instanceof Request ? input.headers : init?.headers!,
body: input instanceof Request ? input.body : init?.body!,
};
const resp = await window.API.trpc(req);
return new Response(resp.body, {
status: resp.status,
headers: resp.headers,
});
},
}),
],
transformer: SuperJSON,
});
// 通过 trpc 对象直接调用 HomeController 的 index 方法
trpc.hello.query().then(res => console.log(res)); // 输出: "Hello from HomeService"
复制代码
总结
我们实现了一个全新的 TRPCServer 组件,虽然这个组件可能稍显简陋,但它成功助力我们的框架在 Electron 环境中运行,这标志着我们在技术探索道路上迈出了坚实的一步。在实际使用过程中,不断涌现的需求推动着我们不断对框架代码进行细致的修改和完善。这是一个持续进化的过程,未来我们还将倾注更多心血,不断完善框架的每一个细节,以提供更加出色、稳定的技术支持。
划线
评论
复制
发布于: 刚刚阅读数: 8
RoyLin
关注
不积跬步 无以至千里 2019-09-01 加入
还未添加个人简介
评论