From c54663d41000d9f10a3ed8089320b121eae285a9 Mon Sep 17 00:00:00 2001 From: neverland Date: Wed, 21 Aug 2024 14:34:18 +0800 Subject: [PATCH] perf: remove compatible API --- src/middleware.js | 52 ++++++++++++---- src/utils/compatibleAPI.js | 107 --------------------------------- test/middleware.test.js | 85 -------------------------- types/middleware.d.ts | 6 ++ types/utils/compatibleAPI.d.ts | 58 ------------------ 5 files changed, 45 insertions(+), 263 deletions(-) delete mode 100644 src/utils/compatibleAPI.js delete mode 100644 types/utils/compatibleAPI.d.ts diff --git a/src/middleware.js b/src/middleware.js index c413205..c8a5430 100644 --- a/src/middleware.js +++ b/src/middleware.js @@ -3,12 +3,6 @@ const mrmime = require("mrmime"); const onFinishedStream = require("on-finished"); const getFilenameFromUrl = require("./utils/getFilenameFromUrl"); -const { - setStatusCode, - send, - pipe, - createReadStreamOrReadFileSync, -} = require("./utils/compatibleAPI"); const ready = require("./utils/ready"); const parseTokenList = require("./utils/parseTokenList"); const memorize = require("./utils/memorize"); @@ -28,6 +22,38 @@ async function getEtag(stat) { return `W/"${size}-${mtime}"`; } +/** + * @typedef {Object} ExpectedResponse + * @property {(status: number) => void} [status] + * @property {(data: any) => void} [send] + * @property {(data: any) => void} [pipeInto] + */ + +/** + * @param {string} filename + * @param {import("./index").OutputFileSystem} outputFileSystem + * @param {number} start + * @param {number} end + * @returns {{ bufferOrStream: (Buffer | import("fs").ReadStream), byteLength: number }} + */ +function createReadStreamOrReadFileSync( + filename, + outputFileSystem, + start, + end, +) { + const bufferOrStream = + /** @type {import("fs").createReadStream} */ + (outputFileSystem.createReadStream)(filename, { + start, + end, + }); + // Handle files with zero bytes + const byteLength = end === 0 ? 0 : end - start + 1; + + return { bufferOrStream, byteLength }; +} + /** * Create a full Content-Type header given a MIME type or extension. * @@ -226,7 +252,7 @@ function wrapper(context) { } // Send basic response - setStatusCode(res, status); + res.statusCode = status; res.setHeader("Content-Type", "text/html; charset=utf-8"); res.setHeader("Content-Security-Policy", "default-src 'none'"); res.setHeader("X-Content-Type-Options", "nosniff"); @@ -520,7 +546,7 @@ function wrapper(context) { // For Koa if (res.statusCode === 404) { - setStatusCode(res, 200); + res.statusCode = 200; } if ( @@ -532,7 +558,7 @@ function wrapper(context) { (res.getHeader("Last-Modified")), }) ) { - setStatusCode(res, 304); + res.statusCode = 304; // Remove content header fields res.removeHeader("Content-Encoding"); @@ -583,7 +609,7 @@ function wrapper(context) { if (parsedRanges !== -2 && parsedRanges.length === 1) { // Content-Range - setStatusCode(res, 206); + res.statusCode = 206; res.setHeader( "Content-Range", getValueContentRangeHeader( @@ -622,7 +648,7 @@ function wrapper(context) { if (req.method === "HEAD") { // For Koa if (res.statusCode === 404) { - setStatusCode(res, 200); + res.statusCode = 200; } res.end(); @@ -635,7 +661,7 @@ function wrapper(context) { ) === "function"; if (!isPipeSupports) { - send(res, /** @type {Buffer} */ (bufferOrStream)); + res.end(/** @type {Buffer} */ (bufferOrStream)); return; } @@ -666,7 +692,7 @@ function wrapper(context) { } }); - pipe(res, /** @type {ReadStream} */ (bufferOrStream)); + /** @type {ReadStream} */ (bufferOrStream).pipe(res); // Response finished, cleanup onFinishedStream(res, cleanup); diff --git a/src/utils/compatibleAPI.js b/src/utils/compatibleAPI.js deleted file mode 100644 index ab315b6..0000000 --- a/src/utils/compatibleAPI.js +++ /dev/null @@ -1,107 +0,0 @@ -/** @typedef {import("../index.js").IncomingMessage} IncomingMessage */ -/** @typedef {import("../index.js").ServerResponse} ServerResponse */ - -/** - * @typedef {Object} ExpectedResponse - * @property {(status: number) => void} [status] - * @property {(data: any) => void} [send] - * @property {(data: any) => void} [pipeInto] - */ - -/** - * @template {ServerResponse & ExpectedResponse} Response - * @param {Response} res - * @param {number} code - */ -function setStatusCode(res, code) { - // Pseudo API - if (typeof res.status === "function") { - res.status(code); - - return; - } - - // Node.js API - // eslint-disable-next-line no-param-reassign - res.statusCode = code; -} - -/** - * @template {ServerResponse} Response - * @param {Response & ExpectedResponse} res - * @param {import("fs").ReadStream} bufferOrStream - */ -function pipe(res, bufferOrStream) { - // Pseudo API and Koa API - if ( - typeof (/** @type {Response & ExpectedResponse} */ (res).pipeInto) === - "function" - ) { - // Writable stream into Readable stream - res.pipeInto(bufferOrStream); - return; - } - - // Node.js API and Express API and Hapi API - bufferOrStream.pipe(res); -} - -/** - * @template {IncomingMessage} Request - * @template {ServerResponse} Response - * @param {Response & ExpectedResponse} res - * @param {string | Buffer} bufferOrStream - */ -function send(res, bufferOrStream) { - // Pseudo API and Express API and Koa API - if (typeof res.send === "function") { - res.send(bufferOrStream); - return; - } - - res.end(bufferOrStream); -} - -/** - * @param {string} filename - * @param {import("../index").OutputFileSystem} outputFileSystem - * @param {number} start - * @param {number} end - * @returns {{ bufferOrStream: (Buffer | import("fs").ReadStream), byteLength: number }} - */ -function createReadStreamOrReadFileSync( - filename, - outputFileSystem, - start, - end, -) { - /** @type {string | Buffer | import("fs").ReadStream} */ - let bufferOrStream; - /** @type {number} */ - let byteLength; - - // Stream logic - const isFsSupportsStream = - typeof outputFileSystem.createReadStream === "function"; - - if (isFsSupportsStream) { - bufferOrStream = - /** @type {import("fs").createReadStream} */ - (outputFileSystem.createReadStream)(filename, { - start, - end, - }); - - // Handle files with zero bytes - byteLength = end === 0 ? 0 : end - start + 1; - } else { - bufferOrStream = - /** @type {import("fs").readFileSync} */ - (outputFileSystem.readFileSync)(filename); - ({ byteLength } = bufferOrStream); - } - - return { bufferOrStream, byteLength }; -} - -module.exports = { setStatusCode, send, pipe, createReadStreamOrReadFileSync }; diff --git a/test/middleware.test.js b/test/middleware.test.js index 8288eee..a86098a 100644 --- a/test/middleware.test.js +++ b/test/middleware.test.js @@ -2736,91 +2736,6 @@ describe.each([ ); }); }); - - describe("should work without `fs.createReadStream`", () => { - let compiler; - let codeContent; - - const outputPath = path.resolve( - __dirname, - "./outputs/basic-test-no-createReadStream", - ); - - beforeAll(async () => { - compiler = getCompiler({ - ...webpackConfig, - output: { - filename: "bundle.js", - path: outputPath, - }, - }); - compiler.hooks.afterCompile.tap("wdm-test", (params) => { - codeContent = params.assets["bundle.js"].source(); - }); - - [server, req, instance] = await frameworkFactory( - name, - framework, - compiler, - ); - - instance.context.outputFileSystem.mkdirSync(outputPath, { - recursive: true, - }); - instance.context.outputFileSystem.writeFileSync( - path.resolve(outputPath, "image.svg"), - "svg image", - ); - - instance.context.outputFileSystem.createReadStream = null; - }); - - afterAll(async () => { - await close(server, instance); - }); - - it('should return the "200" code for the "GET" request to the bundle file', async () => { - const response = await req.get("/bundle.js"); - - expect(response.statusCode).toEqual(200); - expect(response.headers["content-length"]).toEqual( - String(Buffer.byteLength(codeContent)), - ); - expect(response.headers["content-type"]).toEqual( - "text/javascript; charset=utf-8", - ); - expect(response.text).toEqual(codeContent); - }); - - it('should return the "200" code for the "GET" request to the "image.svg" file', async () => { - const fileData = instance.context.outputFileSystem.readFileSync( - path.resolve(outputPath, "image.svg"), - ); - - const response = await req.get("/image.svg"); - - expect(response.statusCode).toEqual(200); - expect(response.headers["content-length"]).toEqual( - fileData.byteLength.toString(), - ); - expect(response.headers["content-type"]).toEqual("image/svg+xml"); - }); - - it('should return the "200" code for the "HEAD" request to the "image.svg" file', async () => { - const fileData = instance.context.outputFileSystem.readFileSync( - path.resolve(outputPath, "image.svg"), - ); - - const response = await req.head("/image.svg"); - - expect(response.statusCode).toEqual(200); - expect(response.headers["content-length"]).toEqual( - fileData.byteLength.toString(), - ); - expect(response.headers["content-type"]).toEqual("image/svg+xml"); - expect(response.body).toEqual({}); - }); - }); }); describe("watchOptions option", () => { diff --git a/types/middleware.d.ts b/types/middleware.d.ts index e1ea86b..047e1c3 100644 --- a/types/middleware.d.ts +++ b/types/middleware.d.ts @@ -20,6 +20,7 @@ declare function wrapper< declare namespace wrapper { export { Stats, + ExpectedResponse, SendErrorOptions, NextFunction, IncomingMessage, @@ -29,6 +30,11 @@ declare namespace wrapper { }; } type Stats = import("fs").Stats; +type ExpectedResponse = { + status?: ((status: number) => void) | undefined; + send?: ((data: any) => void) | undefined; + pipeInto?: ((data: any) => void) | undefined; +}; /** * send error options */ diff --git a/types/utils/compatibleAPI.d.ts b/types/utils/compatibleAPI.d.ts deleted file mode 100644 index b3d9d1a..0000000 --- a/types/utils/compatibleAPI.d.ts +++ /dev/null @@ -1,58 +0,0 @@ -export type IncomingMessage = import("../index.js").IncomingMessage; -export type ServerResponse = import("../index.js").ServerResponse; -export type ExpectedResponse = { - status?: ((status: number) => void) | undefined; - send?: ((data: any) => void) | undefined; - pipeInto?: ((data: any) => void) | undefined; -}; -/** @typedef {import("../index.js").IncomingMessage} IncomingMessage */ -/** @typedef {import("../index.js").ServerResponse} ServerResponse */ -/** - * @typedef {Object} ExpectedResponse - * @property {(status: number) => void} [status] - * @property {(data: any) => void} [send] - * @property {(data: any) => void} [pipeInto] - */ -/** - * @template {ServerResponse & ExpectedResponse} Response - * @param {Response} res - * @param {number} code - */ -export function setStatusCode< - Response extends ServerResponse & ExpectedResponse, ->(res: Response, code: number): void; -/** - * @template {IncomingMessage} Request - * @template {ServerResponse} Response - * @param {Response & ExpectedResponse} res - * @param {string | Buffer} bufferOrStream - */ -export function send< - Request extends IncomingMessage, - Response extends ServerResponse, ->(res: Response & ExpectedResponse, bufferOrStream: string | Buffer): void; -/** - * @template {ServerResponse} Response - * @param {Response & ExpectedResponse} res - * @param {import("fs").ReadStream} bufferOrStream - */ -export function pipe( - res: Response & ExpectedResponse, - bufferOrStream: import("fs").ReadStream, -): void; -/** - * @param {string} filename - * @param {import("../index").OutputFileSystem} outputFileSystem - * @param {number} start - * @param {number} end - * @returns {{ bufferOrStream: (Buffer | import("fs").ReadStream), byteLength: number }} - */ -export function createReadStreamOrReadFileSync( - filename: string, - outputFileSystem: import("../index").OutputFileSystem, - start: number, - end: number, -): { - bufferOrStream: Buffer | import("fs").ReadStream; - byteLength: number; -};