webpack
Version:
Packs ECMAScript/CommonJs/AMD modules for the browser. Allows you to split your codebase into multiple bundles, which can be loaded on demand. Supports loaders to preprocess files, i.e. json, jsx, es7, css, less, ... and your custom stuff.
183 lines (146 loc) • 4.58 kB
JavaScript
/*
MIT License http://www.opensource.org/licenses/mit-license.php
*/
"use strict";
const { extname } = require("path");
const db = require("mime-db");
const memoize = require("./memoize");
/**
* RegExp to match type in RFC 6838
*
* type-name = restricted-name
* subtype-name = restricted-name
* restricted-name = restricted-name-first *126restricted-name-chars
* restricted-name-first = ALPHA / DIGIT
* restricted-name-chars = ALPHA / DIGIT / "!" / "#" / "$" / "&" / "-" / "^" / "_"
* restricted-name-chars =/ "." ; Characters before first dot always specify a facet name
* restricted-name-chars =/ "+" ; Characters after last plus always specify a structured syntax suffix
* ALPHA = %x41-5A / %x61-7A ; A-Z / a-z
* DIGIT = %x30-39 ; 0-9
*/
const TYPE_REGEXP =
/^ *(([A-Za-z0-9][A-Za-z0-9!#$&^_-]{0,126})\/([A-Za-z0-9][A-Za-z0-9!#$&^_.+-]{0,126})) *(?:;.*)?$/;
// Score RFC facets (see https://tools.ietf.org/html/rfc6838#section-3)
/** @type {Record<string, number>} */
const FACET_SCORES = {
"prs.": 100,
"x-": 200,
"x.": 300,
"vnd.": 400,
default: 900
};
/** @typedef {"nginx" | "apache" | "iana" | "default"} SourceScore */
// Score mime source (Logic originally from `jshttp/mime-types` module)
/** @type {Record<SourceScore, number>} */
const SOURCE_SCORES = {
nginx: 10,
apache: 20,
iana: 40,
default: 30
};
/** @type {Record<string, number>} */
const TYPE_SCORES = {
// prefer application/xml over text/xml
// prefer application/rtf over text/rtf
application: 1,
// prefer font/woff over application/font-woff
font: 2,
// prefer video/mp4 over audio/mp4 over application/mp4
// See https://www.rfc-editor.org/rfc/rfc4337.html#section-2
audio: 2,
video: 3,
default: 0
};
/**
* @param {string} mimeType mime type
* @param {SourceScore=} source source
* @returns {number} min score
*/
function mimeScore(mimeType, source = "default") {
if (mimeType === "application/octet-stream") {
return 0;
}
const [type, subtype] = mimeType.split("/");
const facet = subtype.replace(/(\.|x-).*/, "$1");
const facetScore = FACET_SCORES[facet] || FACET_SCORES.default;
const sourceScore = SOURCE_SCORES[source] || SOURCE_SCORES.default;
const typeScore = TYPE_SCORES[type] || TYPE_SCORES.default;
// All else being equal prefer shorter types
const lengthScore = 1 - mimeType.length / 100;
return facetScore + sourceScore + typeScore + lengthScore;
}
/**
* @param {string} ext extension
* @param {string} type0 the first type
* @param {string} type1 the second type
* @returns {string} preferred type
*/
const preferredType = (ext, type0, type1) => {
const score0 = type0 ? mimeScore(type0, db[type0].source) : 0;
const score1 = type1 ? mimeScore(type1, db[type1].source) : 0;
return score0 > score1 ? type0 : type1;
};
/**
* @param {Record<string, readonly string[]>} extensions extensions
* @param {Record<string, string>} types types
*/
const populate = (extensions, types) => {
for (const type of Object.keys(db)) {
const mime = db[type];
const foundExtensions = mime.extensions;
if (!foundExtensions || foundExtensions.length === 0) {
continue;
}
// mime -> extensions
extensions[type] = foundExtensions;
// extension -> mime
for (let i = 0; i < foundExtensions.length; i++) {
const extension = foundExtensions[i];
types[extension] = preferredType(extension, types[extension], type);
}
}
};
// ~2500 mime-db entries scored against each other; only a build that resolves
// a type pays for the tables
const getTables = memoize(() => {
const extensions = Object.create(null);
const types = Object.create(null);
populate(extensions, types);
return { extensions, types };
});
/**
* Get the default extension for a MIME type.
* @param {string} type type
* @returns {undefined | string} resolve extension
*/
const extension = (type) => {
if (!type) {
return;
}
const match = TYPE_REGEXP.exec(type);
if (!match) {
return;
}
const possibleExtensions = getTables().extensions[match[1].toLowerCase()];
if (!possibleExtensions || possibleExtensions.length === 0) {
return;
}
return possibleExtensions[0];
};
/**
* Lookup the MIME type for a file path/extension.
* @param {string} path path
* @returns {undefined | string} resolved MIME type
*/
const lookup = (path) => {
if (!path) {
return;
}
// get the extension ("ext" or ".ext" or full path)
const extension = extname(`x.${path}`).toLowerCase().slice(1);
if (!extension) {
return;
}
return getTables().types[extension];
};
module.exports = { extension, lookup };