diff --git a/examples/getting-started/server.ts b/examples/getting-started/server.ts index d294add..e564064 100644 --- a/examples/getting-started/server.ts +++ b/examples/getting-started/server.ts @@ -1,6 +1,8 @@ import path from "path"; import express from "express"; -import {createSession, Session} from "better-sse"; +import * as SSE from "better-sse/http2"; + +console.log(SSE); const app = express(); @@ -11,7 +13,7 @@ app.use(express.static(path.resolve(__dirname, "./public"))); */ declare module "express-serve-static-core" { interface Response { - sse: Session; + // sse: Session; } } @@ -21,9 +23,9 @@ app.get( * Attach the session instance to the response. */ async (req, res, next) => { - const session = await createSession(req, res); + // const session = await createSession(req, res); - res.sse = session; + // res.sse = session; next(); }, @@ -31,7 +33,7 @@ app.get( * Push a message with the event name "ping". */ (_, res) => { - res.sse.push("Hello world!", "ping"); + // res.sse.push("Hello world!", "ping"); } ); diff --git a/examples/http2-compat/index.ts b/examples/http2-compat/index.ts new file mode 100644 index 0000000..7f8b957 --- /dev/null +++ b/examples/http2-compat/index.ts @@ -0,0 +1 @@ +import "./server"; diff --git a/examples/http2-compat/public/index.html b/examples/http2-compat/public/index.html new file mode 100644 index 0000000..b8f8d9c --- /dev/null +++ b/examples/http2-compat/public/index.html @@ -0,0 +1,10 @@ + + + + + HTTP/2 - Better SSE + + + + + diff --git a/examples/http2-compat/public/index.js b/examples/http2-compat/public/index.js new file mode 100644 index 0000000..d5ce6bd --- /dev/null +++ b/examples/http2-compat/public/index.js @@ -0,0 +1,11 @@ +const eventSource = new EventSource("/sse"); + +eventSource.addEventListener("ping", (event) => { + const {type, data} = event; + + const element = document.createElement("pre"); + + element.innerText = `Got '${type}' event: ${data}.`; + + document.body.appendChild(element); +}); diff --git a/examples/http2-compat/server.ts b/examples/http2-compat/server.ts new file mode 100644 index 0000000..d54234f --- /dev/null +++ b/examples/http2-compat/server.ts @@ -0,0 +1,69 @@ +import {resolve} from "path"; +import {promisify} from "util"; +import {createSecureServer} from "http2"; +import { + CertificateCreationOptions, + CertificateCreationResult, + createCertificate as createCertificateCallback, +} from "pem"; +import {Http2CompatSession} from "better-sse"; + +(async () => { + const createCertificate = promisify< + CertificateCreationOptions, + CertificateCreationResult + >(createCertificateCallback); + + const indexHtmlPath = resolve(__dirname, "./public/index.html"); + const indexJsPath = resolve(__dirname, "./public/index.js"); + + const {serviceKey: key, certificate: cert} = await createCertificate({ + selfSigned: true, + days: 1, + }); + + const server = createSecureServer({key, cert}, async (req, res) => { + const {":path": path, ":method": method} = req.headers; + const {stream} = res; + + if (method !== "GET") { + stream.respond({":status": 405}); + stream.end(); + return; + } + + switch (path) { + case "/": { + stream.respondWithFile(indexHtmlPath); + break; + } + case "/index.js": { + stream.respondWithFile(indexJsPath); + break; + } + case "/sse": { + const session = new Http2CompatSession(req, res); + + session.once("connected", () => { + session.push("Hello world", "ping"); + }); + + break; + } + default: { + stream.respond({":status": 404}); + stream.end(); + } + } + }); + + server.on("error", console.error); + + const PORT = process.env.PORT ?? 8443; + + server.listen(PORT, () => { + console.log( + `Server listening. Open https://localhost:${PORT} in your browser.` + ); + }); +})(); diff --git a/examples/http2/server.ts b/examples/http2/server.ts index ccccbbb..5764c7b 100644 --- a/examples/http2/server.ts +++ b/examples/http2/server.ts @@ -6,7 +6,7 @@ import { CertificateCreationResult, createCertificate as createCertificateCallback, } from "pem"; -import {createSession} from "better-sse"; +import {Http2Session} from "better-sse"; (async () => { const createCertificate = promisify< @@ -22,9 +22,10 @@ import {createSession} from "better-sse"; days: 1, }); - const server = createSecureServer({key, cert}, async (req, res) => { - const {":path": path, ":method": method} = req.headers; - const {stream} = res; + const server = createSecureServer({key, cert}); + + server.on("stream", (stream, headers) => { + const {":path": path, ":method": method} = headers; if (method !== "GET") { stream.respond({":status": 405}); @@ -42,14 +43,17 @@ import {createSession} from "better-sse"; break; } case "/sse": { - const session = await createSession(req, res); + const session = new Http2Session(stream, headers); - session.push("Hello world", "ping"); + session.once("connected", () => { + session.push("Hello world", "ping"); + }); break; } default: { stream.respond({":status": 404}); + stream.end(); } } }); diff --git a/examples/package-lock.json b/examples/package-lock.json index c201cbd..8bec4df 100644 --- a/examples/package-lock.json +++ b/examples/package-lock.json @@ -10,7 +10,7 @@ "license": "MIT", "dependencies": { "benchmark": "^2.1.4", - "better-sse": "^0.12.1", + "better-sse": "file:..", "easy-server-sent-events": "^1.0.14", "eventsource": "^2.0.2", "express": "^4.19.2", @@ -28,6 +28,33 @@ "@types/pem": "^1.14.4" } }, + "..": { + "version": "0.13.0", + "license": "MIT", + "devDependencies": { + "@types/eventsource": "^1.1.11", + "@types/express": "^4.17.16", + "@types/node": "^18.11.18", + "@typescript-eslint/eslint-plugin": "^5.49.0", + "@typescript-eslint/parser": "^5.49.0", + "eslint": "^8.32.0", + "eslint-plugin-tsdoc": "^0.2.17", + "eventsource": "^2.0.2", + "npm-run-all": "^4.1.5", + "prettier": "^2.8.3", + "rimraf": "^4.1.2", + "ts-loader": "^9.4.2", + "ts-node": "^10.9.1", + "typescript": "^4.9.4", + "vitest": "^0.28.2", + "webpack": "^5.75.0", + "webpack-cli": "^5.0.1" + }, + "engines": { + "node": ">=12", + "pnpm": ">=9" + } + }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -311,14 +338,8 @@ } }, "node_modules/better-sse": { - "version": "0.12.1", - "resolved": "https://registry.npmjs.org/better-sse/-/better-sse-0.12.1.tgz", - "integrity": "sha512-U8BiD7DjQGG72kUiCJ5NM5ioKCqVeE+8Nd3pOPFtYx6PO1s0YtZtwNmImdTqSSmGaqDCmfzNwXcXdiAB4mCJrQ==", - "license": "MIT", - "engines": { - "node": ">=12", - "pnpm": ">=9" - } + "resolved": "..", + "link": true }, "node_modules/binary-extensions": { "version": "2.3.0", diff --git a/examples/package.json b/examples/package.json index bd90d79..1683a9a 100644 --- a/examples/package.json +++ b/examples/package.json @@ -9,7 +9,7 @@ }, "dependencies": { "benchmark": "^2.1.4", - "better-sse": "^0.12.1", + "better-sse": "file:..", "easy-server-sent-events": "^1.0.14", "eventsource": "^2.0.2", "express": "^4.19.2", diff --git a/examples/tsconfig.json b/examples/tsconfig.json index 0dd40a8..8653e86 100644 --- a/examples/tsconfig.json +++ b/examples/tsconfig.json @@ -3,7 +3,7 @@ "target": "ES2019", "strict": true, "outDir": "./build", - "moduleResolution": "node", + "moduleResolution": "nodenext", "esModuleInterop": true } } diff --git a/package.json b/package.json index 4e02fb4..6619e8e 100644 --- a/package.json +++ b/package.json @@ -2,8 +2,6 @@ "name": "better-sse", "description": "Dead simple, dependency-less, spec-compliant server-sent events implementation for Node, written in TypeScript.", "version": "0.13.0", - "main": "./build/index.js", - "types": "./build/index.d.ts", "license": "MIT", "author": "Matthew W. ", "repository": "github:MatthewWid/better-sse", @@ -17,6 +15,10 @@ "tcp", "events" ], + "engines": { + "node": ">=12", + "pnpm": ">=9" + }, "scripts": { "build": "webpack --env production", "test": "vitest", @@ -25,14 +27,16 @@ "lint": "eslint \"./src/**/*.ts\"", "prepublishOnly": "npm-run-all clean format test build" }, + "main": "./build/http1.js", + "types": "./build/adapters/http1/index.d.ts", + "exports": { + ".": "./build/http1.js", + "./http2": "./build/http2.js" + }, "files": [ "build", "!build/**/*.map" ], - "engines": { - "node": ">=12", - "pnpm": ">=9" - }, "devDependencies": { "@types/eventsource": "^1.1.11", "@types/express": "^4.17.16", diff --git a/src/Session.ts b/src/Session.ts index dc5b9f6..027f85e 100644 --- a/src/Session.ts +++ b/src/Session.ts @@ -1,9 +1,4 @@ -import { - IncomingMessage as Http1ServerRequest, - ServerResponse as Http1ServerResponse, - OutgoingHttpHeaders, -} from "http"; -import {Http2ServerRequest, Http2ServerResponse} from "http2"; +import {OutgoingHttpHeaders} from "http"; import {EventBuffer, EventBufferOptions} from "./EventBuffer"; import {TypedEmitter, EventMap} from "./lib/TypedEmitter"; import {generateId} from "./lib/generateId"; @@ -98,7 +93,9 @@ interface SessionEvents extends EventMap { * @param res - The Node HTTP {@link https://nodejs.org/api/http.html#http_class_http_serverresponse | IncomingMessage} object. * @param options - Options given to the session instance. */ -class Session extends TypedEmitter { +abstract class Session< + State = DefaultSessionState +> extends TypedEmitter { /** * The last event ID sent to the client. * @@ -128,22 +125,6 @@ class Session extends TypedEmitter { state: State; private buffer: EventBuffer; - - /** - * Raw HTTP request. - */ - private req: Http1ServerRequest | Http2ServerRequest; - - /** - * Raw HTTP response that is the minimal interface needed and forms the - * intersection between the HTTP/1.1 and HTTP/2 server response interfaces. - */ - private res: - | Http1ServerResponse - | (Http2ServerResponse & { - write: (chunk: string) => void; - }); - private serialize: SerializerFunction; private sanitize: SanitizerFunction; private trustClientEventId: boolean; @@ -153,15 +134,17 @@ class Session extends TypedEmitter { private statusCode: number; private headers: OutgoingHttpHeaders; - constructor( - req: Http1ServerRequest | Http2ServerRequest, - res: Http1ServerResponse | Http2ServerResponse, - options: SessionOptions = {} - ) { - super(); + protected abstract getDefaultHeaders(): OutgoingHttpHeaders; + protected abstract getHeader(name: string): string | undefined; + protected abstract getParam(name: string): string | undefined; + protected abstract sendHead( + statusCode: number, + headers: OutgoingHttpHeaders + ): void; + protected abstract sendChunk(chunk: string): void; - this.req = req; - this.res = res; + constructor(options: SessionOptions = {}) { + super(); const serializer = options.serializer ?? serialize; const sanitizer = options.sanitizer ?? sanitize; @@ -185,54 +168,33 @@ class Session extends TypedEmitter { this.state = options.state ?? ({} as State); - this.req.once("close", this.onDisconnected); - this.res.once("close", this.onDisconnected); - setImmediate(this.initialize); } - private initialize = () => { - const url = `http://${this.req.headers.host}${this.req.url}`; - const params = new URL(url).searchParams; - + protected initialize = () => { if (this.trustClientEventId) { const givenLastEventId = - this.req.headers["last-event-id"] ?? - params.get("lastEventId") ?? - params.get("evs_last_event_id") ?? + this.getHeader("last-event-id") ?? + this.getParam("lastEventId") ?? + this.getParam("evs_last_event_id") ?? ""; this.lastId = givenLastEventId as string; } - const headers: OutgoingHttpHeaders = {}; - - if (this.res instanceof Http1ServerResponse) { - headers["Content-Type"] = "text/event-stream"; - headers["Cache-Control"] = - "private, no-cache, no-store, no-transform, must-revalidate, max-age=0"; - headers["Connection"] = "keep-alive"; - headers["Pragma"] = "no-cache"; - headers["X-Accel-Buffering"] = "no"; - } else { - headers["content-type"] = "text/event-stream"; - headers["cache-control"] = - "private, no-cache, no-store, no-transform, must-revalidate, max-age=0"; - headers["pragma"] = "no-cache"; - headers["x-accel-buffering"] = "no"; - } + const headers = this.getDefaultHeaders(); for (const [name, value] of Object.entries(this.headers)) { headers[name] = value ?? ""; } - this.res.writeHead(this.statusCode, headers); + this.sendHead(this.statusCode, headers); - if (params.has("padding")) { + if (this.getParam("padding") !== undefined) { this.buffer.comment(" ".repeat(2049)).dispatch(); } - if (params.has("evs_preamble")) { + if (this.getParam("evs_preamble") !== undefined) { this.buffer.comment(" ".repeat(2056)).dispatch(); } @@ -254,10 +216,7 @@ class Session extends TypedEmitter { this.emit("connected"); }; - private onDisconnected = () => { - this.req.removeListener("close", this.onDisconnected); - this.res.removeListener("close", this.onDisconnected); - + protected onDisconnected() { if (this.keepAliveTimer) { clearInterval(this.keepAliveTimer); } @@ -265,7 +224,7 @@ class Session extends TypedEmitter { this.isConnected = false; this.emit("disconnected"); - }; + } private keepAlive = () => { this.buffer.comment().dispatch(); @@ -334,7 +293,7 @@ class Session extends TypedEmitter { * @deprecated see https://github.com/MatthewWid/better-sse/issues/52 */ flush = (): this => { - this.res.write(this.buffer.read()); + this.sendChunk(this.buffer.read()); this.buffer.clear(); @@ -419,7 +378,7 @@ class Session extends TypedEmitter { batcher: EventBuffer | ((buffer: EventBuffer) => void | Promise) ) => { if (batcher instanceof EventBuffer) { - this.res.write(batcher.read()); + this.sendChunk(batcher.read()); } else { const buffer = new EventBuffer({ serializer: this.serialize, @@ -428,7 +387,7 @@ class Session extends TypedEmitter { await batcher(buffer); - this.res.write(buffer.read()); + this.sendChunk(buffer.read()); } }; } diff --git a/src/adapters/http1/Http1Session.ts b/src/adapters/http1/Http1Session.ts new file mode 100644 index 0000000..244f1d4 --- /dev/null +++ b/src/adapters/http1/Http1Session.ts @@ -0,0 +1,62 @@ +import { + IncomingMessage as Http1ServerRequest, + ServerResponse as Http1ServerResponse, + OutgoingHttpHeaders, +} from "http"; +import {DefaultSessionState, Session} from "../../Session"; + +class Http1Session extends Session { + private params: URLSearchParams; + + constructor( + private req: Http1ServerRequest, + private res: Http1ServerResponse, + ...args: ConstructorParameters> + ) { + super(...args); + + req.once("close", this.onDisconnected); + res.once("close", this.onDisconnected); + + const url = new URL(`http://localhost${req.url}`); + this.params = url.searchParams; + } + + protected onDisconnected = () => { + this.req.removeListener("close", this.onDisconnected); + this.res.removeListener("close", this.onDisconnected); + + super.onDisconnected(); + }; + + protected getDefaultHeaders() { + return { + "Content-Type": "text/event-stream", + "Cache-Control": + "private, no-cache, no-store, no-transform, must-revalidate, max-age=0", + Connection: "keep-alive", + Pragma: "no-cache", + "X-Accel-Buffering": "no", + }; + } + + protected getHeader(name: string): string | undefined { + const value = this.req.headers[name]; + + return Array.isArray(value) ? value.join(",") : value; + } + + protected getParam(name: string): string | undefined { + return this.params.get(name) ?? undefined; + } + + protected sendHead(statusCode: number, headers: OutgoingHttpHeaders) { + this.res.writeHead(statusCode, headers); + } + + protected sendChunk(chunk: string) { + this.res.write(chunk); + } +} + +export {Http1Session}; diff --git a/src/adapters/http1/createHttp1Session.ts b/src/adapters/http1/createHttp1Session.ts new file mode 100644 index 0000000..542c8f3 --- /dev/null +++ b/src/adapters/http1/createHttp1Session.ts @@ -0,0 +1,18 @@ +import type {DefaultSessionState} from "../../Session"; +import {Http1Session} from "./Http1Session"; + +/** + * Create a new session and return the session instance once it has connected. + */ +const createHttp1Session = ( + ...args: ConstructorParameters> +): Promise> => + new Promise((resolve) => { + const session = new Http1Session(...args); + + session.once("connected", () => { + resolve(session); + }); + }); + +export {createHttp1Session}; diff --git a/src/adapters/http1/index.ts b/src/adapters/http1/index.ts new file mode 100644 index 0000000..e62ca99 --- /dev/null +++ b/src/adapters/http1/index.ts @@ -0,0 +1,3 @@ +export * from "../.."; +export * from "./Http1Session"; +export {createHttp1Session as createSession} from "./createHttp1Session"; diff --git a/src/adapters/http2/Http2Session.ts b/src/adapters/http2/Http2Session.ts new file mode 100644 index 0000000..cb0c58a --- /dev/null +++ b/src/adapters/http2/Http2Session.ts @@ -0,0 +1,61 @@ +import { + ServerHttp2Stream, + IncomingHttpHeaders, + OutgoingHttpHeaders, +} from "http2"; +import {DefaultSessionState, Session} from "../../Session"; + +class Http2Session extends Session { + private responseStream: ServerHttp2Stream; + private incomingHeaders: IncomingHttpHeaders; + private params: URLSearchParams; + + constructor( + stream: ServerHttp2Stream, + headers: IncomingHttpHeaders, + ...args: ConstructorParameters> + ) { + super(...args); + + this.responseStream = stream; + this.incomingHeaders = headers; + + this.responseStream.on("close", this.onDisconnected); + + const url = new URL(`http://localhost${headers[":path"]}`); + this.params = url.searchParams; + } + + protected getDefaultHeaders() { + return { + "content-type": "text/event-stream", + "cache-control": + "private, no-cache, no-store, no-transform, must-revalidate, max-age=0", + pragma: "no-cache", + "x-accel-buffering": "no", + }; + } + + protected getHeader(name: string): string | undefined { + const {[name]: value} = this.incomingHeaders; + + return Array.isArray(value) ? value.join(",") : value; + } + + protected getParam(name: string): string | undefined { + return this.params.get(name) ?? undefined; + } + + protected sendHead(statusCode: number, headers: OutgoingHttpHeaders) { + this.responseStream.respond({ + ":status": statusCode, + ...headers, + }); + } + + protected sendChunk(chunk: string) { + this.responseStream.write(chunk); + } +} + +export {Http2Session}; diff --git a/src/adapters/http2/createHttp2Session.ts b/src/adapters/http2/createHttp2Session.ts new file mode 100644 index 0000000..1948d3c --- /dev/null +++ b/src/adapters/http2/createHttp2Session.ts @@ -0,0 +1,17 @@ +import {Http2Session} from "./Http2Session"; + +/** + * Create a new session and return the session instance once it has connected. + */ +const createHttp2Session = ( + ...args: ConstructorParameters> +): Promise> => + new Promise((resolve) => { + const session = new Http2Session(...args); + + session.once("connected", () => { + resolve(session); + }); + }); + +export {createHttp2Session}; diff --git a/src/adapters/http2/index.ts b/src/adapters/http2/index.ts new file mode 100644 index 0000000..58780a2 --- /dev/null +++ b/src/adapters/http2/index.ts @@ -0,0 +1,2 @@ +export * from "./Http2Session"; +export {createHttp2Session as createSession} from "./createHttp2Session"; diff --git a/src/adapters/http2compat/Http2CompatSession.ts b/src/adapters/http2compat/Http2CompatSession.ts new file mode 100644 index 0000000..ec5aa7c --- /dev/null +++ b/src/adapters/http2compat/Http2CompatSession.ts @@ -0,0 +1,61 @@ +import { + Http2ServerRequest, + Http2ServerResponse, + OutgoingHttpHeaders, +} from "http2"; +import {DefaultSessionState, Session} from "../../Session"; + +class Http2CompatSession extends Session { + private params: URLSearchParams; + + constructor( + private req: Http2ServerRequest, + private res: Http2ServerResponse, + ...args: ConstructorParameters> + ) { + super(...args); + + req.once("close", this.onDisconnected); + res.once("close", this.onDisconnected); + + const url = new URL(`http://localhost${req.url}`); + this.params = url.searchParams; + } + + protected onDisconnected() { + this.req.removeListener("close", this.onDisconnected); + this.res.removeListener("close", this.onDisconnected); + + super.onDisconnected(); + } + + protected getDefaultHeaders() { + return { + "content-type": "text/event-stream", + "cache-control": + "private, no-cache, no-store, no-transform, must-revalidate, max-age=0", + pragma: "no-cache", + "x-accel-buffering": "no", + }; + } + + protected getHeader(name: string): string | undefined { + const value = this.req.headers[name]; + + return Array.isArray(value) ? value.join(",") : value; + } + + protected getParam(name: string): string | undefined { + return this.params.get(name) ?? undefined; + } + + protected sendHead(statusCode: number, headers: OutgoingHttpHeaders) { + this.res.writeHead(statusCode, headers); + } + + protected sendChunk(chunk: string) { + this.res.write(chunk); + } +} + +export {Http2CompatSession}; diff --git a/src/adapters/http2compat/createHttp2CompatSession.ts b/src/adapters/http2compat/createHttp2CompatSession.ts new file mode 100644 index 0000000..c5f6b59 --- /dev/null +++ b/src/adapters/http2compat/createHttp2CompatSession.ts @@ -0,0 +1,17 @@ +import {Http2CompatSession} from "./Http2CompatSession"; + +/** + * Create a new session and return the session instance once it has connected. + */ +const createHttp2CompatSession = ( + ...args: ConstructorParameters> +): Promise> => + new Promise((resolve) => { + const session = new Http2CompatSession(...args); + + session.once("connected", () => { + resolve(session); + }); + }); + +export {createHttp2CompatSession}; diff --git a/src/adapters/http2compat/index.ts b/src/adapters/http2compat/index.ts new file mode 100644 index 0000000..d354266 --- /dev/null +++ b/src/adapters/http2compat/index.ts @@ -0,0 +1,2 @@ +export * from "./Http2CompatSession"; +export {createHttp2CompatSession as createSession} from "./createHttp2CompatSession"; diff --git a/src/createSession.test.ts b/src/createSession.test.ts deleted file mode 100644 index 29f346e..0000000 --- a/src/createSession.test.ts +++ /dev/null @@ -1,37 +0,0 @@ -import {it, expect, beforeEach, afterEach} from "vitest"; -import http from "http"; -import EventSource from "eventsource"; -import {createHttpServer, closeServer, getUrl} from "./lib/testUtils"; -import {Session} from "./Session"; -import {createSession} from "./createSession"; - -let server: http.Server; -let url: string; -let eventsource: EventSource; - -beforeEach(async () => { - server = await createHttpServer(); - - url = getUrl(server); -}); - -afterEach(async () => { - if (eventsource && eventsource.readyState !== 2) { - eventsource.close(); - } - - await closeServer(server); -}); - -it("resolves with an instance of a session", () => - new Promise((done) => { - server.on("request", async (req, res) => { - const session = await createSession(req, res); - - expect(session).toBeInstanceOf(Session); - - done(); - }); - - eventsource = new EventSource(url); - })); diff --git a/src/createSession.ts b/src/createSession.ts deleted file mode 100644 index 85018e0..0000000 --- a/src/createSession.ts +++ /dev/null @@ -1,17 +0,0 @@ -import {Session} from "./Session"; - -/** - * Create a new session and return the session instance once it has connected. - */ -const createSession = ( - ...args: ConstructorParameters> -): Promise> => - new Promise((resolve) => { - const session = new Session(...args); - - session.once("connected", () => { - resolve(session); - }); - }); - -export {createSession}; diff --git a/src/index.ts b/src/index.ts index 7b23969..3100564 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,4 @@ export * from "./Session"; -export * from "./createSession"; export * from "./Channel"; export * from "./createChannel"; diff --git a/webpack.config.ts b/webpack.config.ts index d0c281d..d57032b 100644 --- a/webpack.config.ts +++ b/webpack.config.ts @@ -8,7 +8,8 @@ const config = (): Configuration => ({ mode: "production", devtool: "source-map", entry: { - index: "./src/index.ts", + http1: "./src/adapters/http1/index.ts", + http2: "./src/adapters/http2/index.ts", }, output: { filename: "[name].js",