diff --git a/.cspell.json b/.cspell.json index ddea2f0..aaf3e30 100644 --- a/.cspell.json +++ b/.cspell.json @@ -17,6 +17,7 @@ "myhtml", "configurated", "mycustom", + "mrmime", "commitlint", "nosniff", "deoptimize", diff --git a/README.md b/README.md index 45ac08d..fa00530 100644 --- a/README.md +++ b/README.md @@ -166,7 +166,7 @@ Default: `undefined` This property allows a user to register custom mime types or extension mappings. eg. `mimeTypes: { phtml: 'text/html' }`. -Please see the documentation for [`mime-types`](https://github.com/jshttp/mime-types) for more information. +Please see the documentation for [`mrmime`](https://github.com/lukeed/mrmime) for more information. ### mimeTypeDefault diff --git a/package.json b/package.json index 3294c45..a077e2f 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ }, "dependencies": { "memfs": "^4.43.1", - "mime-types": "^3.0.1", + "mrmime": "^2.0.1", "on-finished": "^2.4.1", "range-parser": "^1.2.1" }, @@ -55,7 +55,6 @@ "@rstest/core": "0.9.2", "@types/connect": "^3.4.35", "@types/express": "^5.0.2", - "@types/mime-types": "^3.0.1", "@types/node": "^22.3.0", "@types/on-finished": "^2.3.4", "@types/range-parser": "^1.2.7", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0ccdf81..65ec798 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,9 +11,9 @@ importers: memfs: specifier: ^4.43.1 version: 4.56.11(tslib@2.8.1) - mime-types: - specifier: ^3.0.1 - version: 3.0.2 + mrmime: + specifier: ^2.0.1 + version: 2.0.1 on-finished: specifier: ^2.4.1 version: 2.4.1 @@ -51,9 +51,6 @@ importers: '@types/express': specifier: ^5.0.2 version: 5.0.6 - '@types/mime-types': - specifier: ^3.0.1 - version: 3.0.1 '@types/node': specifier: ^22.3.0 version: 22.19.13 @@ -1337,9 +1334,6 @@ packages: '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - '@types/mime-types@3.0.1': - resolution: {integrity: sha512-xRMsfuQbnRq1Ef+C+RKaENOxXX87Ygl38W1vDfPHRku02TgQr+Qd8iivLtAMcR0KF5/29xlnFihkTlbqFrGOVQ==} - '@types/node@22.19.13': resolution: {integrity: sha512-akNQMv0wW5uyRpD2v2IEyRSZiR+BeGuoB6L310EgGObO44HSMNT8z1xzio28V8qOrgYaopIDNA18YgdXd+qTiw==} @@ -2500,6 +2494,10 @@ packages: minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + mrmime@2.0.1: + resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} + engines: {node: '>=10'} + ms@2.0.0: resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} @@ -4665,8 +4663,6 @@ snapshots: '@types/json-schema@7.0.15': {} - '@types/mime-types@3.0.1': {} - '@types/node@22.19.13': dependencies: undici-types: 6.21.0 @@ -6063,6 +6059,8 @@ snapshots: dependencies: brace-expansion: 1.1.12 + mrmime@2.0.1: {} + ms@2.0.0: {} ms@2.1.3: {} diff --git a/src/index.js b/src/index.js index 001d552..ea63f3d 100644 --- a/src/index.js +++ b/src/index.js @@ -1,4 +1,4 @@ -const mime = require("mime-types"); +const { mimes } = require("mrmime"); const middleware = require("./middleware"); const getFilenameFromUrl = require("./utils/getFilenameFromUrl"); @@ -203,12 +203,11 @@ function wdm(compiler, options = {}) { const { mimeTypes } = options; if (mimeTypes) { - const { types } = mime; - - // mimeTypes from user provided options should take priority - // over existing, known types - // @ts-expect-error - mime.types = { ...types, ...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; + } } /** diff --git a/src/middleware.js b/src/middleware.js index 7b38ea9..733d3d5 100644 --- a/src/middleware.js +++ b/src/middleware.js @@ -1,7 +1,6 @@ const path = require("node:path"); -const mime = require("mime-types"); - +const { lookup, mimes } = require("mrmime"); const onFinishedStream = require("on-finished"); const { @@ -34,6 +33,35 @@ const ready = require("./utils/ready"); /** @typedef {import("fs").ReadStream} ReadStream */ const BYTES_RANGE_REGEXP = /^ *bytes/i; +const UTF8_CHARSET_MIME_TYPES = new Set([ + "application/javascript", + "application/json", + "application/manifest+json", +]); + +mimes.usdz = "model/vnd.usdz+zip"; + +/** + * @param {string} filename filename or extension + * @returns {string | undefined} content type header value + */ +function getContentType(filename) { + const mimeType = lookup(filename); + + if (!mimeType) { + return undefined; + } + + if (mimeType.includes(";")) { + return mimeType; + } + + if (mimeType.startsWith("text/") || UTF8_CHARSET_MIME_TYPES.has(mimeType)) { + return `${mimeType}; charset=utf-8`; + } + + return mimeType; +} /** * @param {"bytes"} type type @@ -688,7 +716,7 @@ function wrapper(context) { ) { removeResponseHeader(res, "Content-Type"); // content-type name (like application/javascript; charset=utf-8) or false - const contentType = mime.contentType(path.extname(filename)); + const contentType = getContentType(path.extname(filename)); // 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 97d1045..fd72791 100644 --- a/test/middleware.test.js +++ b/test/middleware.test.js @@ -11,7 +11,7 @@ import finalhandler from "finalhandler"; import { Hono } from "hono"; import koa from "koa"; import memfs, { Volume, createFsFromVolume } from "memfs"; -import mime from "mime-types"; +import { lookup, mimes } from "mrmime"; import router from "router"; import request from "supertest"; import { Stats } from "@rspack/core"; @@ -34,6 +34,14 @@ import getCompilerHooks from "./helpers/getCompilerHooks"; // Suppress unnecessary stats output rs.spyOn(globalThis.console, "log").mockImplementation(); +const UTF8_CHARSET_MIME_TYPES = new Set([ + "application/javascript", + "application/json", + "application/manifest+json", +]); + +mimes.usdz = "model/vnd.usdz+zip"; + async function startServer(name, app) { return new Promise((resolve, reject) => { if (name === "router") { @@ -270,7 +278,21 @@ function get404ContentTypeHeader(name) { } function getContentTypeHeader(name, ext = "js") { - return mime.contentType(ext); + const mimeType = lookup(ext); + + if (!mimeType) { + return undefined; + } + + if (mimeType.includes(";")) { + return mimeType; + } + + if (mimeType.startsWith("text/") || UTF8_CHARSET_MIME_TYPES.has(mimeType)) { + return `${mimeType}; charset=utf-8`; + } + + return mimeType; } function applyTestMiddleware(name, middlewares) {