This article is participating in node.js advanced technology essay, click to see more details

Mime is a very rich mime type module of Js. For example, you can get the MIME type based on the file extension, or you can get the MIME type based on the MIME type. As of this writing, the library has 1.8K stars and over 50 million downloads per week, showing its popularity. But its source code implementation is relatively simple, very suitable for just started to read the source partners, enhance confidence must oh!! This article will explain the basic use and implementation of MIME based on version 3.0.0. Same old rule, no idle talk, let’s do it!!

MIME concept

A media type (often called a Multipurpose Internet Mail Extensions or MIME type) is a standard used to represent the nature and format of a document, file, or byte stream. It is defined and standardized in IETF RFC 6838.

MIME syntax is as follows:

type/subtype
Copy the code
  • Used between strings of type and subtype/Separate from
  • typeRepresents a separate category that can be subclassed.subtypeRepresents each subdivided type
  • Case insensitive, but the traditional way of writing it is lowercase
  • Spaces are not allowed

Common important MIME types are:

  • application/octet-streamDefault value for an application file that browsers generally do not automatically execute or ask to execute. The browser sets the HTTP header as if it wereContent-DispositionA value ofattachmentTreat these files as if they were files.
  • text/plainThe default value for text files, even if it means unknown text files that the browser thinks can be displayed directly.
  • text/cssAnything to be resolved to CSS in a web pageCSSFiles must be specifiedMIMEfortext/css, pay special attention toCSSThe file provides the correct MIME type.
  • text/htmlAll HTML content should use this type
  • text/javascriptAccording to the HTML standard, javascript files should always be served using the MIME type text/javascript. Other values are not considered valid, and using those values may cause the script not to load or run

All MIME types can be found here

Mime Usage Examples

Here is a basic usage example:

const mime = require('./index');

// Get MIME through extension
const cssMimeType = mime.getType('css');
// Get MIME from path
const cssMimeType2 = mime.getType('dir/demo.css');

// text/css
console.log(cssMimeType);
// text/css
console.log(cssMimeType2);

// Get the extension through MIME
const cssExt = mime.getExtension(cssMimeType);
// css
console.log(cssExt);
Copy the code

Mime source code analysis

The source file for the MIME library is shown below:

  • The part in the red box is the complete implementation, whereindex.jsEntry file,typesUnder the folder is the raw data dictionary
  • The green part is the easy version implementation,lite.jsIt’s an entry file
  • The blue part is the command line implementation, just onecli.jsfile

To see the full MIME implementation, open the index.js file:

'use strict';

// Import Mime classes
let Mime = require('./Mime');

// Expose an instance of Mime
// The class takes two MIME-related dictionaries of raw data
module.exports = new Mime(require('./types/standard'), require('./types/other'));
Copy the code

Let’s look at Mime implementation:

'use strict';

/ * * *@param typeMap [Object] Map of MIME type -> Array[extensions]
 * @param . * /
function Mime() {
  * {CSS: 'text/ CSS ', * HTML: 'text/ HTML ', * HTM: 'text/ HTML ', * SHTML: 'text/html', * } */
  this._types = Object.create(null);
  /** * _extensions store mimeType -> ext mapping * TODO: in the data dictionary ext is multiple values, only the first value is bound to mimeType * and the first value is bound to mimeType ** {* 'text/ CSS ': 'CSS' * 'text/HTML:' HTML ', / / in the data dictionary "text/HTML" : [" HTML ", ".htm ", "SHTML"] * 'text/RTF' : 'the RTF, / / in the data dictionary "text/RTF" : ["*rtf"] * } */
  this._extensions = Object.create(null);

  // If an argument is passed during initialization, a new MIME type is added for the parameters of the define method
  for (let i = 0; i < arguments.length; i++) {
    this.define(arguments[i]);
  }

  // bind the this scope
  this.define = this.define.bind(this);
  this.getType = this.getType.bind(this);
  this.getExtension = this.getExtension.bind(this);
}

/** * Define the mapping of mimeType -> extension */
Mime.prototype.define = function(typeMap, force) {};/** * Get mimeType */ from path or extension
Mime.prototype.getType = function(path) {};/** * Get the default extension */ via mimeType
Mime.prototype.getExtension = function(type) {};module.exports = Mime;
Copy the code

The Mime class contains the following main properties and methods:

  • _typesProperties, storageext -> mimeTypeThe mapping of
  • _extensionsProperties, storagemimeType -> extThe mapping of
  • defineMethod used to definemimetype -> extensionThe mapping of
  • getTypeBy means ofpathorextensionTo obtainmimetype
  • getExtensionBy means ofmimetypeGet the defaultextension

The Mime constructor’s main instantiation logic is to iterate over all the parameters, each of which is a dictionary of Mime type data, and call define on each dictionary to generate a mapping between ext -> Mime and Mime -> ext. The format of the parameter data passed to the Mime class can be seen in the standard.js file:

module.exports = {
  "application/andrew-inset": ["ez"]."application/applixware": ["aw"]."application/atom+xml": ["atom"]./ /... More data
  "text/html": ["html"."htm"."shtml"]."text/jade": ["jade"]."text/jsx": ["jsx"]."text/less": ["less"]."text/markdown": ["markdown"."md"]./ /... More data
}
Copy the code

Note here that multiple file types can be mapped to the same MIME, so the MIME value is an extension array.

Let’s move on to the define method, right, and explore what it does:

/** * Define the mapping of mimeType -> extension. * Each key is a MimeType and the value is an array of extensions associated with that MimeType. * The first value of the extension array is the default extension value for this mimeType. * * e.g. mime.define({'audio/ogg', ['oga', 'ogg', 'spx']}); If an extension has already been defined, an error will be thrown. * Overwriting can be done by setting the second parameter 'force' to true. * * e.g. mime.define({'audio/wav', ['wav']}, {'audio/x-wav', ['*wav']}); * *@param The map (Object) type defines data *@param Force (Boolean) Overwrite */ if the value is true
Mime.prototype.define = function(typeMap, force) {
  // Iterate over type definition data objects
  for (let type in typeMap) {
    // Get the collection of extensions associated with the current mimeType
    let extensions = typeMap[type].map(function(t) {
      return t.toLowerCase();
    });
    type = type.toLowerCase();

    // Traverse the Extensions with each item as key and the corresponding MimeType as value binding mapping
    for (let i = 0; i < extensions.length; i++) {
      const ext = extensions[i];

      // '*' prefix = not the preferred type for this extension. So fixup the
      // extension, and skip it.
      if (ext[0= = =The '*') {
        continue;
      }

      // If the extension is already defined, it is thrown
      if(! force && (extin this._types)) {
        throw new Error(
          'Attempt to change mapping for "' + ext +
          '" extension from "' + this._types[ext] + '" to "' + type +
          '". Pass `force=true` to allow this, otherwise remove "' + ext +
          '" from the list of extensions for "' + type + '".
        );
      }

      this._types[ext] = type;
    }

    // use the first entry in the extension collection as the default extension for mimeType
    if (force || !this._extensions[type]) {
      const ext = extensions[0];
      this._extensions[type] = (ext[0]! = =The '*')? ext : ext.substr(1); }}};Copy the code

The define logic is basically written in the annotations. The core is to iterate over the dictionary data, and then scale the dictionary data into a one-to-one mapping between MIME and Ext. Use text/ HTML as an example:

// The original HTML MIME data format
{
  "text/html": ["html"."htm"."shtml"],}// Leveled _types
{
  "html": "text/html"."htm": "text/html"."shtml": "text/html"
}
// Flattened _extensions
{
  "text/html": "html",}Copy the code

Now that we’ve flattened the data, let’s look at how we get the MIME type. The getType method is implemented as follows:

/** * Get mimeType */ from path or extension
Mime.prototype.getType = function(path) {
  path = String(path);
  /** * last gets basename * TODO: this is not done with path.basename because * needs to support the 'dir\\text. TXT 'argument type * EG: CSS => CSS, text/ CSS => CSS, dir\\text.txt => text.txt */
  let last = path.replace(/ ^. * / / \ \] /.' ').toLowerCase();
  * EG: CSS => CSS, a.css => CSS, a.css. CSS => CSS */
  let ext = last.replace(/ ^. * \. /.' ').toLowerCase();

  // If the length of last is smaller than that of path, it indicates that the path parameter is passed, not the extension parameter
  let hasPath = last.length < path.length;
  // The length of ext is smaller than that of last. The extension of the
  let hasDot = ext.length < last.length - 1;

  // If the extension parameter is passed directly, such as CSS
  // Or pass a path with an extension, such as dir/demo.css
  // Return the corresponding mimeType, otherwise return null
  return(hasDot || ! hasPath) &&this._types[ext] || null;
};
Copy the code

The getType method takes the path or extension argument, parses the extension value, and retrieves the MIME value based on the previous _types dictionary. It is important to note that path.basename is not used to obtain basename because dir\\deno. TXT is supported.

Finally, let’s look at how to get an extension from MIME. GetExtension is implemented as follows:

/** * Get the default extension */ via mimeType
Mime.prototype.getExtension = function(type) {
  type = /^\s*([^;\s]*)/.test(type) && RegExp. $1;return type && this._extensions[type.toLowerCase()] || null;
};
Copy the code

The logic here is to return the extension based on the MIME value, but the core is how the re gets the MIME type.

// Regex indicates that there can be 0 to n Spaces before and after, and any 0 to n characters other than semicolons and Spaces will be matched
Return RegExp.$1, the contents of the first subexpression
// Returns the matched content in the parentheses
type = /^\s*([^;\s]*)/.test(type) && RegExp. $1;Copy the code

Why do this when you pass in values like text/ CSS? So if you look at MIME for HTML a lot of times it’s text/ HTML; Charset = UTf8, with encoding related content, so this re is just the text/ HTML part of the value to handle these cases.

Mime lite implementation

The mime library’s author exposed lite.js to provide a simplified version of the MIME library, which is implemented in Lite.js:

'use strict';

let Mime = require('./Mime');
module.exports = new Mime(require('./types/standard'));
Copy the code

The difference from the full version is that other’s MIME data is dropped.

Mime command line implementation

Mime library command line implementation in cli.js, complete implementation as follows:

#! /usr/bin/env node

'use strict';

process.title = 'mime';
let mime = require('. ');
let pkg = require('./package.json');
// Get the command line footer
let args = process.argv.splice(2);

// Get the version
if (args.includes('--version') || args.includes('-v') || args.includes('--v')) {
  console.log(pkg.version);
  process.exit(0);
// Get the name of the library
} else if (args.includes('--name') || args.includes('-n') || args.includes('--n')) {
  console.log(pkg.name);
  process.exit(0);
// Get help information about the library
} else if (args.includes('--help') || args.includes('-h') || args.includes('--h')) {
  console.log(pkg.name + The '-' + pkg.description + '\n');
  console.log(`Usage: mime [flags] [path_or_extension] Flags: --help, -h Show this message --version, -v Display the version --name, -n Print the name of the program Note: the command will exit after it executes if a command is specified The path_or_extension is the path to the file or the extension of the file. Examples: mime --help mime --version mime --name mime -v mime src/log.js mime new.py mime foo.sh `);
  process.exit(0);
}

// Get file parameters
let file = args[0];
// Call mime to get mime
let type = mime.getType(file);

// Output to the terminal
process.stdout.write(type + '\n');
Copy the code

The main logic is relatively simple, is to get command line parameters, output related information, through the command line file parameters, call mime library to get MIME, and then write to the console through process.stdout.write.

conclusion

If you like this article ❤️❤️ 👍👍 👍👍 tweet ❤️❤️❤️ ha ~ ~ ~

I also recommend you to read my other source code parsing class nuggets article:

  • Interviewer: How does vue-Router parse URL routing parameters? Xiao Ming:……
  • Take a look at how the Node Attachment download service works in the Content-Disposition source code
  • Explain the implementation principle of NodeJs static file hosting service in “Send” source code
  • Explain the Node middleware model architecture in the source code of Connect
  • NodeJS techniques in live-Server source code
  • Ts Master: 22 examples to delve into Ts’s most obscure advanced type tools 👍 1.5K
  • Here are 28 Canvas libraries that will make you scream “wow!” 👍 852
  • In-depth explanation of VsCode advanced debugging and use skills in various scenarios

Hello everyone, I am Leng Hammer, welcome to pay attention to me, I and the front end of the story continues……