diff --git a/src/index.js b/src/index.js index 2a6ed75..7053f2d 100644 --- a/src/index.js +++ b/src/index.js @@ -1,5 +1,3 @@ -import { mimes } from "mrmime"; - import middleware from "./middleware.js"; import getFilenameFromUrl from "./utils/getFilenameFromUrl.js"; import ready from "./utils/ready.js"; @@ -199,16 +197,6 @@ const noop = () => {}; * @returns {API} rspack dev middleware */ function rdm(compiler, options = {}) { - const { mimeTypes } = options; - - if (mimeTypes) { - // mrmime.lookup closes over the exported dictionary, so overrides - // need to update the object in place. - for (const [extension, type] of Object.entries(mimeTypes)) { - mimes[extension.toLowerCase()] = type; - } - } - /** * @type {WithOptional, "watching" | "outputFileSystem">} */ diff --git a/src/middleware.js b/src/middleware.js index df36f72..0823973 100644 --- a/src/middleware.js +++ b/src/middleware.js @@ -1,7 +1,7 @@ import path from "node:path"; import { finished } from "node:stream"; -import { lookup, mimes } from "mrmime"; +import { mimes } from "mrmime"; import rangeParser from "range-parser"; import { @@ -47,14 +47,14 @@ const UTF8_CHARSET_MIME_TYPES = new Set([ "application/manifest+json", ]); -mimes.usdz = "model/vnd.usdz+zip"; - /** - * @param {string} filename filename or extension + * @param {string} extension file extension including the leading dot + * @param {Map} mimeTypes instance MIME mappings * @returns {string | undefined} content type header value */ -function getContentType(filename) { - const mimeType = lookup(filename); +function getContentType(extension, mimeTypes) { + const ext = extension.trim().slice(1).toLowerCase(); + const mimeType = mimeTypes.get(ext) ?? mimes[ext]; if (!mimeType) { return undefined; @@ -172,6 +172,14 @@ const MAX_MAX_AGE = 31536000000; * @returns {import("./index.js").Middleware} wrapper */ function wrapper(context) { + const mimeTypes = new Map([["usdz", "model/vnd.usdz+zip"]]); + + for (const [extension, type] of Object.entries( + context.options.mimeTypes || {}, + )) { + mimeTypes.set(extension.toLowerCase(), type); + } + return async function middleware(req, res, next) { /** * @param {NodeJS.ErrnoException=} err an error @@ -733,7 +741,7 @@ function wrapper(context) { ) { removeResponseHeader(res, "Content-Type"); // content-type name (like application/javascript; charset=utf-8) or false - const contentType = getContentType(path.extname(filename)); + const contentType = getContentType(path.extname(filename), mimeTypes); // Only set content-type header if media type is known // https://tools.ietf.org/html/rfc7231#section-3.1.1.5 diff --git a/test/middleware.test.js b/test/middleware.test.js index 63db620..e033731 100644 --- a/test/middleware.test.js +++ b/test/middleware.test.js @@ -12,7 +12,7 @@ import finalhandler from "finalhandler"; import { Hono } from "hono"; import koa from "koa"; import memfs, { Volume, createFsFromVolume } from "memfs"; -import { lookup, mimes } from "mrmime"; +import { lookup } from "mrmime"; import router from "router"; import request from "supertest"; @@ -47,8 +47,6 @@ const UTF8_CHARSET_MIME_TYPES = new Set([ "application/manifest+json", ]); -mimes.usdz = "model/vnd.usdz+zip"; - async function startServer(name, app) { return new Promise((resolve, reject) => { if (name === "router") { @@ -3907,6 +3905,45 @@ describe.each([ }); describe("mimeTypes option", () => { + it("should isolate MIME overrides between instances", async () => { + const defaultType = lookup("js"); + const expectedTypes = [ + "text/plain; charset=utf-8", + "text/html; charset=utf-8", + getContentTypeHeader(), + ]; + const instances = []; + + try { + for (const mimeTypes of [ + { JS: "text/plain" }, + { js: "text/html" }, + undefined, + ]) { + instances.push( + await frameworkFactory( + name, + framework, + getCompiler(webpackConfig), + { + mimeTypes, + }, + ), + ); + } + + for (const [index, [, request]] of instances.entries()) { + const response = await request.get("/bundle.js").expect(200); + expect(response.headers["content-type"]).toBe(expectedTypes[index]); + } + expect(lookup("js")).toBe(defaultType); + } finally { + await Promise.all( + instances.map(([server, , instance]) => close(server, instance)), + ); + } + }); + describe('should set the correct value for "Content-Type" header to known MIME type', () => { beforeAll(async () => { const outputPath = path.resolve(__dirname, "./outputs/basic");