Skip to content

Commit 7603da7

Browse files
committedOct 17, 2020
feat: remove prod dependency to socket.io-client
The client bundles are included in the repository in order to remove socket.io-client from the list of production dependencies and thus to reduce the total number of dependencies when installing the server. This means the release of the client and the server must now be in sync (which is almost always the case actually). The minified build is now served: - /<path>/socket.io.js - /<path>/socket.io.js.map - /<path>/socket.io.min.js - /<path>/socket.io.min.js.map The content will now be compressed as well.
1 parent a81b9f3 commit 7603da7

File tree

7 files changed

+6206
-69
lines changed

7 files changed

+6206
-69
lines changed
 

‎client-dist/socket.io.js

+6,084
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎client-dist/socket.io.js.map

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎client-dist/socket.io.min.js

+7
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎client-dist/socket.io.min.js.map

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎lib/index.ts

+67-64
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import http from "http";
2-
import { existsSync as exists, readFileSync as read } from "fs";
2+
import { createReadStream } from "fs";
3+
import { createDeflate, createGzip, createBrotliCompress } from "zlib";
4+
import accepts = require("accepts");
5+
import { pipeline } from "stream";
36
import path from "path";
47
import engine from "engine.io";
58
import { Client } from "./client";
@@ -16,14 +19,8 @@ import { CorsOptions } from "cors";
1619

1720
const debug = debugModule("socket.io:server");
1821

19-
const clientVersion = require("socket.io-client/package.json").version;
20-
21-
/**
22-
* Socket.IO client source.
23-
*/
24-
25-
let clientSource = undefined;
26-
let clientSourceMap = undefined;
22+
const clientVersion = require("../package.json").version;
23+
const dotMapRegex = /\.map/;
2724

2825
type Transport = "polling" | "websocket";
2926

@@ -175,6 +172,7 @@ export class Server extends EventEmitter {
175172
private eio;
176173
private engine;
177174
private _path: string;
175+
private clientPathRegex: RegExp;
178176

179177
/**
180178
* @private
@@ -220,27 +218,6 @@ export class Server extends EventEmitter {
220218
public serveClient(v?: boolean): Server | boolean {
221219
if (!arguments.length) return this._serveClient;
222220
this._serveClient = v;
223-
const resolvePath = function(file) {
224-
const filepath = path.resolve(__dirname, "./../../", file);
225-
if (exists(filepath)) {
226-
return filepath;
227-
}
228-
return require.resolve(file);
229-
};
230-
if (v && !clientSource) {
231-
clientSource = read(
232-
resolvePath("socket.io-client/dist/socket.io.js"),
233-
"utf-8"
234-
);
235-
try {
236-
clientSourceMap = read(
237-
resolvePath("socket.io-client/dist/socket.io.js.map"),
238-
"utf-8"
239-
);
240-
} catch (err) {
241-
debug("could not load sourcemap file");
242-
}
243-
}
244221
return this;
245222
}
246223

@@ -290,7 +267,13 @@ export class Server extends EventEmitter {
290267
public path(): string;
291268
public path(v?: string): Server | string {
292269
if (!arguments.length) return this._path;
270+
293271
this._path = v.replace(/\/$/, "");
272+
273+
const escapedPath = this._path.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&");
274+
this.clientPathRegex = new RegExp(
275+
"^" + escapedPath + "/socket\\.io(\\.min)?\\.js(\\.map)?$"
276+
);
294277
return this;
295278
}
296279

@@ -410,16 +393,12 @@ export class Server extends EventEmitter {
410393
*/
411394
private attachServe(srv) {
412395
debug("attaching client serving req handler");
413-
const url = this._path + "/socket.io.js";
414-
const urlMap = this._path + "/socket.io.js.map";
396+
415397
const evs = srv.listeners("request").slice(0);
416-
const self = this;
417398
srv.removeAllListeners("request");
418-
srv.on("request", function(req, res) {
419-
if (0 === req.url.indexOf(urlMap)) {
420-
self.serveMap(req, res);
421-
} else if (0 === req.url.indexOf(url)) {
422-
self.serve(req, res);
399+
srv.on("request", (req, res) => {
400+
if (this.clientPathRegex.test(req.url)) {
401+
this.serve(req, res);
423402
} else {
424403
for (let i = 0; i < evs.length; i++) {
425404
evs[i].call(srv, req, res);
@@ -429,62 +408,86 @@ export class Server extends EventEmitter {
429408
}
430409

431410
/**
432-
* Handles a request serving `/socket.io.js`
411+
* Handles a request serving of client source and map
433412
*
434413
* @param {http.IncomingMessage} req
435414
* @param {http.ServerResponse} res
436415
* @private
437416
*/
438417
private serve(req: http.IncomingMessage, res: http.ServerResponse) {
418+
const filename = req.url.replace(this._path, "");
419+
const isMap = dotMapRegex.test(filename);
420+
const type = isMap ? "map" : "source";
421+
439422
// Per the standard, ETags must be quoted:
440423
// https://tools.ietf.org/html/rfc7232#section-2.3
441424
const expectedEtag = '"' + clientVersion + '"';
442425

443426
const etag = req.headers["if-none-match"];
444427
if (etag) {
445428
if (expectedEtag == etag) {
446-
debug("serve client 304");
429+
debug("serve client %s 304", type);
447430
res.writeHead(304);
448431
res.end();
449432
return;
450433
}
451434
}
452435

453-
debug("serve client source");
436+
debug("serve client %s", type);
437+
454438
res.setHeader("Cache-Control", "public, max-age=0");
455-
res.setHeader("Content-Type", "application/javascript");
439+
res.setHeader(
440+
"Content-Type",
441+
"application/" + (isMap ? "json" : "javascript")
442+
);
456443
res.setHeader("ETag", expectedEtag);
457-
res.writeHead(200);
458-
res.end(clientSource);
444+
445+
if (!isMap) {
446+
res.setHeader("X-SourceMap", filename.substring(1) + ".map");
447+
}
448+
Server.sendFile(filename, req, res);
459449
}
460450

461451
/**
462-
* Handles a request serving `/socket.io.js.map`
463-
*
464-
* @param {http.IncomingMessage} req
465-
* @param {http.ServerResponse} res
452+
* @param filename
453+
* @param req
454+
* @param res
466455
* @private
467456
*/
468-
private serveMap(req: http.IncomingMessage, res: http.ServerResponse) {
469-
// Per the standard, ETags must be quoted:
470-
// https://tools.ietf.org/html/rfc7232#section-2.3
471-
const expectedEtag = '"' + clientVersion + '"';
457+
private static sendFile(
458+
filename: string,
459+
req: http.IncomingMessage,
460+
res: http.ServerResponse
461+
) {
462+
const readStream = createReadStream(
463+
path.join(__dirname, "../client-dist/", filename)
464+
);
465+
const encoding = accepts(req).encodings(["br", "gzip", "deflate"]);
472466

473-
const etag = req.headers["if-none-match"];
474-
if (etag) {
475-
if (expectedEtag == etag) {
476-
debug("serve client 304");
477-
res.writeHead(304);
467+
const onError = err => {
468+
if (err) {
478469
res.end();
479-
return;
480470
}
481-
}
471+
};
482472

483-
debug("serve client sourcemap");
484-
res.setHeader("Content-Type", "application/json");
485-
res.setHeader("ETag", expectedEtag);
486-
res.writeHead(200);
487-
res.end(clientSourceMap);
473+
switch (encoding) {
474+
case "br":
475+
res.writeHead(200, { "content-encoding": "br" });
476+
readStream.pipe(createBrotliCompress()).pipe(res);
477+
pipeline(readStream, createBrotliCompress(), res, onError);
478+
break;
479+
case "gzip":
480+
res.writeHead(200, { "content-encoding": "gzip" });
481+
pipeline(readStream, createGzip(), res, onError);
482+
break;
483+
case "deflate":
484+
res.writeHead(200, { "content-encoding": "deflate" });
485+
pipeline(readStream, createDeflate(), res, onError);
486+
break;
487+
default:
488+
res.writeHead(200);
489+
pipeline(readStream, res, onError);
490+
}
488491
}
489492

490493
/**

‎package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,11 @@
3434
"prepack": "tsc"
3535
},
3636
"dependencies": {
37+
"accepts": "~1.3.4",
3738
"base64id": "~2.0.0",
3839
"debug": "~4.1.0",
3940
"engine.io": "~4.0.0",
4041
"socket.io-adapter": "2.0.3-rc1",
41-
"socket.io-client": "3.0.0-rc2",
4242
"socket.io-parser": "4.0.1-rc2"
4343
},
4444
"devDependencies": {
@@ -53,6 +53,7 @@
5353
"mocha": "^3.5.3",
5454
"nyc": "^11.2.1",
5555
"prettier": "^1.19.1",
56+
"socket.io-client": "3.0.0-rc2",
5657
"superagent": "^3.8.2",
5758
"supertest": "^3.0.0",
5859
"ts-node": "^9.0.0",

‎test/socket.io.ts

+44-4
Original file line numberDiff line numberDiff line change
@@ -35,21 +35,61 @@ describe("socket.io", () => {
3535
describe("http.Server", () => {
3636
const clientVersion = require("socket.io-client/package.json").version;
3737

38-
it("should serve static files", done => {
38+
const testSource = filename => done => {
3939
const srv = createServer();
4040
new Server(srv);
4141
request(srv)
42-
.get("/socket.io/socket.io.js")
42+
.get("/socket.io/" + filename)
43+
.buffer(true)
44+
.end((err, res) => {
45+
if (err) return done(err);
46+
expect(res.headers["content-type"]).to.be("application/javascript");
47+
expect(res.headers.etag).to.be('"' + clientVersion + '"');
48+
expect(res.headers["x-sourcemap"]).to.be(filename + ".map");
49+
expect(res.text).to.match(/engine\.io/);
50+
expect(res.status).to.be(200);
51+
done();
52+
});
53+
};
54+
55+
const testSourceMap = filename => done => {
56+
const srv = createServer();
57+
new Server(srv);
58+
request(srv)
59+
.get("/socket.io/" + filename)
4360
.buffer(true)
4461
.end((err, res) => {
4562
if (err) return done(err);
46-
const ctype = res.headers["content-type"];
47-
expect(ctype).to.be("application/javascript");
63+
expect(res.headers["content-type"]).to.be("application/json");
4864
expect(res.headers.etag).to.be('"' + clientVersion + '"');
4965
expect(res.text).to.match(/engine\.io/);
5066
expect(res.status).to.be(200);
5167
done();
5268
});
69+
};
70+
71+
it("should serve client", testSource("socket.io.js"));
72+
it("should serve source map", testSourceMap("socket.io.js.map"));
73+
it("should serve client (min)", testSource("socket.io.min.js"));
74+
75+
it(
76+
"should serve source map (min)",
77+
testSourceMap("socket.io.min.js.map")
78+
);
79+
80+
it("should serve client (gzip)", done => {
81+
const srv = createServer();
82+
new Server(srv);
83+
request(srv)
84+
.get("/socket.io/socket.io.js")
85+
.set("accept-encoding", "gzip,br,deflate")
86+
.buffer(true)
87+
.end((err, res) => {
88+
if (err) return done(err);
89+
expect(res.headers["content-encoding"]).to.be("gzip");
90+
expect(res.status).to.be(200);
91+
done();
92+
});
5393
});
5494

5595
it("should handle 304", done => {

0 commit comments

Comments
 (0)
Please sign in to comment.