当前位置:网站首页>Let's talk about how the 15.5K file saver works?

Let's talk about how the 15.5K file saver works?

2020-12-08 08:58:16 A Bao Ge

FileSaver.js It's a solution to save files on the client side , Very suitable for generating files on the client side Web Applications . It's easy to use and compatible with most browsers , Used as a project dependency in 6.3 Ten thousand projects . In recent projects , Once again, apocalypse used it , So I want to write an article to talk about this excellent open source project .

One 、FileSaver.js brief introduction

FileSaver.js yes HTML5 Of saveAs() FileSaver Realization . Most browsers support , Its compatibility is shown in the figure below :

( picture source :https://github.com/eligrey/Fi...

Focus on 「 The road of building immortals in the whole stack 」 Read the original 3 This free e-book ( Cumulative download nearly 2 ten thousand ) And 50 Several articles “ Relearning TS” course .

1.1 saveAs API

FileSaver saveAs(Blob/File/Url, optional DOMString filename, optional Object { autoBom })

saveAs Method supports three parameters , The first parameter indicates that it supports Blob/File/Url Three types of , The second parameter represents the file name ( Optional ), The third parameter represents the configuration object ( Optional ). If you need FlieSaver.js Automatic provision Unicode Text encoding tips ( Reference resources : Byte order mark ), You need to set { autoBom: true}.

1.2 Save text

let blob = new Blob([" Hello everyone , I'm brother Bao !"], { type: "text/plain;charset=utf-8" });
FileSaver.saveAs(blob, "hello.txt");

1.3 Save online resources

FileSaver.saveAs("https://httpbin.org/image", "image.jpg");

If you download URL The address is in the same domain as the current site , Will use a[download] Way to download . otherwise , Will use first synchronous HEAD request To see if they support it CORS Mechanism , If you support it , Data will be downloaded and used Blob URL Realize file download . If not CORS Mechanism , Will try to use a[download] Way to download .

The standard W3C File API Blob Interfaces are not available in all browsers , For this question , You can consider using Blob.js To solve the compatibility problem .

( picture source :https://caniuse.com/?search=blob

1.4 preservation Canvas Canvas content

let canvas = document.getElementById("my-canvas");
canvas.toBlob(function(blob) {
  saveAs(blob, "abao.png");
});

It should be noted that canvas.toBlob() Methods are not available in all browsers , For this question , You can consider using canvas-toBlob.js To solve the compatibility problem .

( picture source :https://caniuse.com/?search=t...

In the example above , We have seen many times Blob The figure of , So I'm introducing FileSaver.js Source code , I'd like to give you a brief introduction Blob Knowledge about .

Two 、Blob brief introduction

Blob(Binary Large Object) Represents a large object of binary type . In database management system , Storing binary data as a collection of single individuals .Blob It's usually an image 、 Sound or multimedia files . stay JavaScript in Blob Objects of type represent immutable raw data like file objects .

2.1 Blob Constructors

Blob By an optional string type( Usually MIME type ) and blobParts form :

MIME(Multipurpose Internet Mail Extensions) Multipurpose Internet mail extension type , A file with an extension is a type of file opened by an application , When the extension file is accessed , The browser opens automatically using the specified application . Mostly used to specify some client-side customized file names , And some ways to open media files .

common MIME Type a : Hypertext markup language text .html text/html、PNG Images .png image/png、 Plain text .txt text/plain etc. .

stay JavaScript We can pass Blob Constructor to create Blob object ,Blob The syntax of the constructor is as follows :

var aBlob = new Blob(blobParts, options);

The relevant parameters are described as follows :

  • blobParts: It is a result of ArrayBuffer,ArrayBufferView,Blob,DOMString An array of objects .DOMStrings Will be encoded as UTF-8.
  • options: An optional object , There are two properties :

    • type —— The default value is "", It represents that it will be put into blob Of the contents of the array in MIME type .
    • endings —— The default value is "transparent", Used to specify include line terminators \n How the string of is written . It's one of the following two values : "native", The line terminator will be changed to a line break suitable for the host operating system file system , perhaps "transparent", The delegates will keep blob The ending character saved in does not change .

Introduction after Blob after , Let's introduce it again Blob URL.

2.2 Blob URL

Blob URL/Object URL It's a pseudo protocol , allow Blob and File Objects are used as images , Download binary data links, etc URL Source . In the browser , We use URL.createObjectURL Method to create Blob URL, The method receives one Blob object , And create a unique URL, In the form of blob:<origin>/<uuid>, The corresponding example is as follows :

blob:https://example.org/40a5fb5a-d56d-4a33-b4e2-0acf6a8e5f641

Browser internal for each pass URL.createObjectURL Generated URL Stored a URL → Blob mapping . therefore , Such kind URL Shorter , But you can visit Blob. Generated URL Only valid when the current document is open . It allows you to quote <img><a> Medium Blob, But if you visit Blob URL No longer exist , You will receive from the browser 404 error .

Aforementioned Blob URL It looks good , But it actually has side effects . Although it has stored URL → Blob Mapping , but Blob Itself still resides in memory , The browser can't release it . The mapping is automatically cleared when the document is unloaded , therefore Blob The object is then released . however , If the application lives long , That won't happen soon . therefore , If we create a Blob URL, Even if it's no longer necessary to Blob, It will also be in memory .

In response to this question , We can call URL.revokeObjectURL(url) Method , Remove references from internal mappings , This allows deletion Blob( If there is no other reference ), And free up memory .

well , Now we've introduced Blob and Blob URL. If you're still in the middle of something , Want to understand Blob Words , You can read You don't know Blob This article , Next we start to analyze FileSaver.js Source code .

If you want to know how to read the source code , You can read Use these ideas and techniques , I have read many excellent open source projects This article .

3、 ... and 、FileSaver.js The source code parsing

stay FileSaver.js There are three ways to save files , Therefore, we will introduce the three solutions respectively .

3.1 Scheme 1

When FileSaver.js When saving a file , If the current platform a Tag support download Attribute is not MacOS WebView Environmental Science , Will give priority to a[download] To save files . In the specific use process , We call saveAs Method to save the file , The definition of this method is as follows :

FileSaver saveAs(Blob/File/Url, optional DOMString filename, optional Object { autoBom })

Through observation saveAs Method signature , We know that this method supports strings and Blob Two types of parameters , So in saveAs These two types of internal parameters need to be handled separately , Let's first analyze the string parameters .

3.1.1 String type parameters

In the previous example , We demonstrated how to use saveAs Methods to save online images :

FileSaver.saveAs("https://httpbin.org/image", "image.jpg");

In scheme one ,saveAs The processing logic of the method is as follows :

// Use download attribute first if possible (#193 Lumia mobile) unless this is a macOS WebView
function saveAs(blob, name, opts) {
  var URL = _global.URL || _global.webkitURL;
  var a = document.createElement("a");
  name = name || blob.name || "download";

  a.download = name;
  a.rel = "noopener";

  if (typeof blob === "string") {
    a.href = blob;
    if (a.origin !== location.origin) { // (1)
      corsEnabled(a.href)
        ? download(blob, name, opts)
        : click(a, (a.target = "_blank"));
    } else { // (2)
      click(a);
    }
  } else {
    //  Omit processing Blob Type parameter 
  }
}

In the above code , If you find the download resource URL The address is not in the same domain as the current site , You will use synchronous HEAD request To see if they support it CORS Mechanism , If you support it , Will call download Method to download the file . First of all, let's analyze corsEnabled Method :

function corsEnabled(url) {
  var xhr = new XMLHttpRequest();
  xhr.open("HEAD", url, false);
  try {
    xhr.send();
  } catch (e) {}
  return xhr.status >= 200 && xhr.status <= 299;
}

corsEnabled The implementation of this method is very simple , It is through XMLHttpRequest API Initiate a synchronous HEAD request , Then judge whether the returned status code is in [200 ~ 299] Within the scope of . So let's see download The concrete implementation of the method :

function download(url, name, opts) {
  var xhr = new XMLHttpRequest();
  xhr.open("GET", url);
  xhr.responseType = "blob";
  xhr.onload = function () {
    saveAs(xhr.response, name, opts);
  };
  xhr.onerror = function () {
    console.error("could not download file");
  };
  xhr.send();
}

Again download The implementation of the method is also very simple , through XMLHttpRequest API To initiate HTTP request , What you are familiar with JSON The difference is that , We need to set responseType The type of blob. Besides , Because the result of the return is blob Data of type , So it will continue to call inside the successful callback function saveAs Method to save the file .

And for not supporting CORS In the case of a mechanism or domain , It will call the internal click Method to complete the download function , The implementation of this method is as follows :

// `a.click()` doesn't work for all browsers (#465)
function click(node) {
  try {
    node.dispatchEvent(new MouseEvent("click"));
  } catch (e) {
    var evt = document.createEvent("MouseEvents");
    evt.initMouseEvent(
      "click", true, true, window, 0, 0, 0, 80, 20, 
      false, false, false, false, 0, null
    );
    node.dispatchEvent(evt);
  }
}

stay click Methods the internal , Will call... First node On the object dispatchEvent Methods to distribute click event . When something goes wrong , Will be in catch Statement to handle the corresponding exception ,catch Statement MouseEvent.initMouseEvent() Method is used to initialize the value of the mouse event . But it should be noted that , This feature has been developed from Web Delete... From the standard , Although some browsers still support it , But maybe at some point in the future, support will stop , Please try not to use this feature .

3.1.2 blob Type parameter

Again , In the previous example , We demonstrated how to use saveAs Methods to save Blob Type data :

let blob = new Blob([" Hello everyone , I'm brother Bao !"], { type: "text/plain;charset=utf-8" });
FileSaver.saveAs(blob, "hello.txt");

blob Processing logic of type parameters , Is defined in saveAs Methods the body of the else In the branch :

// Use download attribute first if possible (#193 Lumia mobile) unless this is a macOS WebView
function saveAs(blob, name, opts) {
  var URL = _global.URL || _global.webkitURL;
  var a = document.createElement("a");
  name = name || blob.name || "download";

  a.download = name;
  a.rel = "noopener";

  if (typeof blob === "string") {
     //  Omit processing string type parameters 
  } else {
    a.href = URL.createObjectURL(blob);
    setTimeout(function () {
      URL.revokeObjectURL(a.href);
    }, 4e4); // 40s
    setTimeout(function () {
      click(a);
    }, 0);
  }
}

about blob Parameters of type , First it will pass createObjectURL Method to create Object URL, And then through click Method execution file saving . In order to release memory in time , stay else Dealing with branches , Will start a timer to perform the cleanup operation . here , We have already introduced plan one , The next scheme is mainly for compatibility IE browser .

3.2 Option two

stay Internet Explorer 10 Browser ,msSaveBlob and msSaveOrOpenBlob Method allows the user to save the file on the client , among msSaveBlob Method only provides a save button , and msSaveOrOpenBlob Method provides save and open buttons , The corresponding usage is as follows :

window.navigator.msSaveBlob(blobObject, 'msSaveBlob_hello.txt');
window.navigator.msSaveOrOpenBlob(blobObject, 'msSaveBlobOrOpenBlob_hello.txt');

After understanding the above knowledge and scheme 1 introduced in corsEnableddownload and click After the method , Let's look at the code for scenario two , It's very clear . In the meet "msSaveOrOpenBlob" in navigator When the conditions , FileSaver.js Will use scheme two to save the file . It's the same as before , Let's first analyze String type parameters Processing logic .

3.2.1 String type parameters
// Use msSaveOrOpenBlob as a second approach
function saveAs(blob, name, opts) {
  name = name || blob.name || "download";
  if (typeof blob === "string") {
    if (corsEnabled(blob)) { //  Judge whether to support CORS
      download(blob, name, opts);
    } else {
      var a = document.createElement("a");
      a.href = blob;
      a.target = "_blank";
      setTimeout(function () {
        click(a);
      });
    }
  } else {
    //  Omit processing Blob Type parameter 
  }
}
3.2.2 blob Type parameter
// Use msSaveOrOpenBlob as a second approach
function saveAs(blob, name, opts) {
  name = name || blob.name || "download";
  if (typeof blob === "string") {
    //  Omit processing string type parameters 
  } else {
    navigator.msSaveOrOpenBlob(bom(blob, opts), name); //  Provides save and open buttons 
  }
}

3.3 Option three

If neither option 1 nor option 2 supports it ,FileSaver.js It will be degraded FileReader API and open API Open a new window to save files .

3.3.1 String type parameters
// Fallback to using FileReader and a popup
function saveAs(blob, name, opts, popup) {
  // Open a popup immediately do go around popup blocker
  // Mostly only available on user interaction and the fileReader is async so...
  popup = popup || open("", "_blank");
  if (popup) {
    popup.document.title = popup.document.body.innerText = "downloading...";
  }

  if (typeof blob === "string") return download(blob, name, opts);
    //  Handle Blob Type parameter 
}
3.3.2 blob Type parameter

about blob Type parameters , stay saveAs Methods will choose different schemes according to different environments , For example Safari In the browser environment , It will take advantage of FileReader API The first Blob Object to Data URL, And then Data URL Address assigned to the new window or the current window location object , The specific code is as follows :

// Fallback to using FileReader and a popup
function saveAs(blob, name, opts, popup) {
  // Open a popup immediately do go around popup blocker
  // Mostly only available on user interaction and the fileReader is async so...
  popup = popup || open("", "_blank");
  if (popup) { //  Set the title of the new window 
    popup.document.title = popup.document.body.innerText = "downloading...";
  }

  if (typeof blob === "string") return download(blob, name, opts);

  var force = blob.type === "application/octet-stream"; //  Binary stream data 
  var isSafari = /constructor/i.test(_global.HTMLElement) || _global.safari;
  var isChromeIOS = /CriOS\/[\d]+/.test(navigator.userAgent);

  if (
    (isChromeIOS || (force && isSafari) || isMacOSWebView) &&
    typeof FileReader !== "undefined"
  ) {
    // Safari doesn't allow downloading of blob URLs
    var reader = new FileReader();
    reader.onloadend = function () {
      var url = reader.result;
      url = isChromeIOS
        ? url
        : url.replace(/^data:[^;]*;/, "data:attachment/file;"); //  Deal with it in the form of an attachment 
      if (popup) popup.location.href = url;
      else location = url;
      popup = null; // reverse-tabnabbing #460
    };
    reader.readAsDataURL(blob);
  } else {
    //  Omit Object URL Processing logic 
  }
}

In fact, for FileReader API Come on , Except for the support File/Blob Object to Data URL outside , It also provides readAsArrayBuffer() and readAsText() Method , Used for holding File/Blob Object to other data formats . stay Play with the front-end binary In the article , Brother Bao introduced in detail FileReader API Application in front-end image processing scene , After reading the article , You will be able to easily understand the following transformation diagram :

Finally, let's take a look at else Branch code :

function saveAs(blob, name, opts, popup) {
  popup = popup || open("", "_blank");
  if (popup) {
    popup.document.title = popup.document.body.innerText = "downloading...";
  }

  //  Handle string type parameters 
  if (typeof blob === "string") return download(blob, name, opts);

  if (
    (isChromeIOS || (force && isSafari) || isMacOSWebView) &&
    typeof FileReader !== "undefined"
  ) {
    //  Omit FileReader API Processing logic 
  } else {
    var URL = _global.URL || _global.webkitURL;
    var url = URL.createObjectURL(blob);
    if (popup) popup.location = url;
    else location.href = url;
    popup = null; // reverse-tabnabbing #460
    setTimeout(function () {
      URL.revokeObjectURL(url);
    }, 4e4); // 40s
  }
}

Come here FileSaver.js The source code of this library has been analyzed , After reading the above source code with a Bao Ge , Do you think it's good to write a compatibility 、 How difficult is an easy-to-use third-party library . In the actual project , If you need to save more than blob The size limit of large files , Or not enough memory space , You can consider using more advanced StreamSaver.js Library to achieve file saving function .

Focus on 「 The road of building immortals in the whole stack 」 Read the original 3 This free e-book ( Cumulative download nearly 2 ten thousand ) And 8 Source code analysis series of tutorials .

Four 、 The resources

版权声明
本文为[A Bao Ge]所创,转载请带上原文链接,感谢
https://chowdera.com/2020/12/20201208085738326t.html