写点什么

使用 TypeScript 从零搭建自己的 Web 框架: Electron 环境运行

作者:RoyLin
  • 2024-04-16
    江西
  • 本文字数:3028 字

    阅读完需:约 10 分钟

使用 TypeScript 从零搭建自己的 Web 框架: Electron 环境运行

得益于我们框架中引入了依赖注入模式,并通过接口将核心类与组件优雅地解耦,我们仅需稍作调整,便能够轻松地在 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.tsimport { 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.tsimport { Injectable } from '@/core';
@Injectable()export class HomeService { constructor() {}
async sayHello(): Promise<string> { return 'Hello from HomeService'; }}
复制代码


实现 TRPCServer


简单实现一个 TRPCServer, 并将控制器方法与 tRPC 路由绑定。


// core/component/TRPCServer.tsimport { 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.tsimport { contextBridge, ipcRenderer } from 'electron';
process.once('loaded', async () => { contextBridge.exposeInMainWorld('API', { trpc: (req: IpcRequest) => ipcRenderer.invoke('trpc', req), });});
复制代码


在渲染进程中调用方法


// renderer.tsimport { 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 环境中运行,这标志着我们在技术探索道路上迈出了坚实的一步。在实际使用过程中,不断涌现的需求推动着我们不断对框架代码进行细致的修改和完善。这是一个持续进化的过程,未来我们还将倾注更多心血,不断完善框架的每一个细节,以提供更加出色、稳定的技术支持。


用户头像

RoyLin

关注

不积跬步 无以至千里 2019-09-01 加入

还未添加个人简介

评论

发布
暂无评论
使用 TypeScript 从零搭建自己的 Web 框架: Electron 环境运行_typescript_RoyLin_InfoQ写作社区